在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
文章名字好难起哦,既想能清楚的表达本文的主旨,又想短小精悍,真难。 为啥要“亲自”呢?我想表达的意思是,在自已写的程序中自已控制一切,这就叫亲自。说起这个词,还有一个典故(真人真事,如果雷同,实属巧合): 在我上高三时,四班有一个位同学姓黄,名**,他以前在三中,后来转学到一中的。该黄姓同学一直在追求一种境界,到底是一种什么样的境界,很难描述...他可以在课堂上肆无忌惮的排放腹中废气,也可以在毫无征兆的情况下打个方圆30m之内其他房间可以听到的喷嚏,甚至可以和校长开玩笑,一次,他课间去嘘嘘,本来是不允许学生去教师的WC的,但他是无视这种规定的,他先到的,正在放水时校长进来了,他就和校长打了个招呼:“高校长,您亲自来上厕所了?”,校长被憋的竟只得"唔,唔"搪塞过去... 很是怀念校园的时光啊 进入正题: 为啥需要“亲自”呢,这得说明来龙去脉:我的本意是做一个RESTful服务,自已写了一个Atom的Client,使用WebRequest向服务器提交数据,当然,格式是Atom10的,在服务器端使用Request把Client传上来的数据拿出来,如下伪代码所示: public ActionResult Create() { var entry = GetFromRequest(); //... } 这样好么?刚写好时觉得不错,但第二眼就觉得不好了,为啥,如果这个entry是从Create方法的参数中传来的该多好啊,想起以前在学习WCF时遇到的一个问题,使用Atom10FeedFormatter类就可以在参数中获得这个entry的实例了,何乐不为呢,于是代码变成这样: public ActionResult Create(Atom10FeedFormatter<LogEntry> log) { //... } 不幸的是,按照之前经验,在该方法内部可以通过log.Item就获得从Client传来的实体了,但是在这里发现log.Item居然为null。 这才发现原来MVC和WCF Syndication的机制是不同的啊。 通过阅读MVC的源代码发现,ControllerActionInvoker中一个方法GetParameterValues,这个方法获取Action的参数列表,然后把根据一些策略生成对应的参数值,但是这个GetParameterValues似乎只能通过重载才能达到我想要的目的,达到我的目的又如何保证不影响MVC本来的意图呢?还是先试试不重载,看有没有轻量级的解决方案吧。 又想起来MVC中Filter不是几乎无所不能么,要么来个ActionFitler好了,于是定义了一个类型: [AttributeUsage(AttributeTargets.Method)] public class AtomEntryConvertAttribute : Attribute, IActionFilter { public void OnActionExecuting(ActionExecutingContext filterContext) { //... } public void OnActionExecuted(ActionExecutedContext filterContext) { } } 试图在OnActionExecuting中从Request中读取数据,结果很快发现了问题,Action都执行了,该特性类的方法还没有执行,仔细查看MVC的源代码,发现了问题所在: protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) { ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters); Func<ActionExecutedContext> continuation = () => new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) { Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters) }; // need to reverse the filter list because the continuations are built up backward Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation, (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next)); return thunk(); } Action的调用在ActionMethodFitler之前...汗啊,真是不长记性,上次就在这里被“骗”一次,时间不长居然又忘了这茬了。 又回到GetParameterValues方法... (此处省去2小时的思考,尝试过程) 有一点结论了,如果要实现轻量级“非侵入”式的操作,以M$的习惯,一般是使用Attribute或者反射的,这里用反射似乎不妥,那么就应该在Entry类型或是参数本身上应用Attribute比较合适,根据M$的命名习惯,这种事情应该使用诸如Custom、Convert、Parameter之类的名称,带着这个思路一找,果然发现了一个类型CustomModelBinderAttribute。 光是这个类型名,就感觉是干这个事情的,自定义 模型 绑定 特性 ,看起来像了,该类型中有一个公共方法: public abstract IModelBinder GetBinder(); 刚才在读MVC源代码时就发现这个IModelBinder接口大量使用在GetParameterValues方法中,差不多了,再看IModelBinder的定义: object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext); 看定义就觉得像是从controllerContext和bindingContext中生成一个参数值的。 直接创建一个CustomModelBinderAttribute的子类: [AttributeUsage(AttributeTargets.Parameter)] public class AtomEntryParameterConvertAttribute : CustomModelBinderAttribute { public Type EntryType { get; private set; } public AtomEntryParameterConvertAttribute(Type entryType) :base() { this.EntryType = entryType; } public override IModelBinder GetBinder() { return new AtomEntryConvertModelBinder(this.EntryType); } } 再创建一个IModelBinder的实现: internal class AtomEntryConvertModelBinder : IModelBinder { public Type EntryType { get; private set; } internal AtomEntryConvertModelBinder(Type entryType) { this.EntryType = entryType; } public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var result = AtomServiceHelper.GetDataFromEntry(controllerContext.HttpContext.Request, this.EntryType, bindingContext.ModelType); return result; } } 这里我要稍说明下,其实我在Action的参数中收到的不是直接从客户端上传来的SyndicationItem类型的子类AtomEntry,AtomEntry只是Client和Server交互时的基于Atom协议的数据结构,它派生自SyndicationItem类,真正在Server上使用的是实体类,在我的例子中: public class Log {} Log类型是真正服务端业务层、数据层使用的实体类型 public class LogEntry : AtomEntry {} public class AtomEntry : SyndicationItem {} LogEntry类型是Client与Server交互的数据格式 因此,我不仅需要从Client上把LogEntry的实例传到Server,而且还要在Server上的Action中使用参数直接获得Log类型的实例,当然Log和LogEntry的定义是一个策略,或者说它们之间是有约定的,没有约定,它俩也完不成转换,我喜欢约定甚于配置。 AtomServiceHelper.GetDataFromEntry(controllerContext.HttpContext.Request, this.EntryType, bindingContext.ModelType) 正是AtomServiceHelper类读取Request中的数据,然后将EntryType类型的实例转换为参数类型的实例。 其实对于IModuleBinder我也没有查看MSDN,只是感觉就是这种用法,对不对还没有做实验,于是写个例子: [HttpPost] [ServiceError] public ActionResult Create([AtomEntryParameterConvert(typeof(LogEntry))]LogRecord log) { var logMng = new LogManager(); logMng.CreateLog(log); return new EmptyResult(); } 运行,yeah!果然如愿得到了来自Client的数据 |
请发表评论