ModelBinder在asp.net mvc中可以算是一个亮点,有了ModelBinder我们就可以大胆的传递各种类型的参数给Action了,这些都要归功于强大的DefaultModelBinder,当然它也不是万能的,有时候我们还是需要自己亲手写一个自己的ModelBinder,那么在实际中,ModelBinder的机制是怎么样的呢?当然您完全可以不关心这个而开发出一样漂亮的程序,但我们是孜孜不倦的程序员,因此我仔细阅读了一下源代码,把这些所得的分析给大家,可能具体的大家不用太较真,了解一下这个机制,以及它的流程,纵览一下它的设计,我想对您的帮助也是很大的。好,Let's Go.
内容概览Top
完成ModelBinder有三个步骤Top
在整个ModelBinder的过程中,我把它分成三个部分以便于理解。
- 通过ActionDescriptor获取ParameterDescriptor过程。(捕获参数过程)
- 通过ParameterDescriptor获取IModelBinder过程。(索取绑定者过程)
- 构建ModelBindingContext调用IModelBinder收获结果过程。(调用绑定过程)
几个重要的类Top
在我读源代码时候,采取的是顺藤摸瓜的方式,在经过了若干层的调用之后,我弄明白了牵扯关系的几个类,以及这些类的职责。它们是ControllerActionInvoker、ActionDescriptor(ReflectedActionDescriptor)、ControllerContext、ParameterDescriptor(ReflectedParameterDescriptor)、IModelBinder、ModelBindingContext。
1.ControllerActionInvoker类:这个类的作用非常大,不仅仅是在ModelBinder中,在以前的文章中我也多次提到过。我们知道它是执行Action的一个类,那么Action的参数就由它来处理,它在ModelBinder中扮演的是一个指挥者的角色,它调用其它类的方法从最初的请求中得到最终的各种类型的参数。
2.ActionDescriptor类:这是一个抽象类,在mvc中它的一个实现是ReflectedActionDescriptor类,它在ModelBinder中的作用就是获取ParameterDescriptor对象,也就是我们上面说的“捕获参数过程”这一过程,它通过GetParameters方法返回一个ParameterDescriptor[]类型的对象。这个ParameterDescriptor[]就是Action参数s的描述信息。
public override ParameterDescriptor[] GetParameters() {}
至于ParameterDescriptor对象是如何构建的,这里我们需要想一下ActionDescriptor对象是怎么构建的,当初是通过指定ActionDescriptor类的一个MethodInfo类型的字段来实例一个ActionDescriptor的,ParameterDescriptor跟它有点相似,ParameterDescriptor需要的是一个ParameterInfo类型的字段,那么我们就知道了,ParameterInfo可以通过MethodInfo的GetParameters方法得到,这些都是用到了System.Reflection程序集的反射。
3.ParameterDescriptor类:这个类是描述Action参数的一个类,它主要有两个类型的字段,一个是ParameterInfo,一个是ActionDescriptor,前者指示了它描述了那一个参数,后者指示它属于那一个Action,这两个类型的字段也是在构造时不须提供的。
public ReflectedParameterDescriptor(ParameterInfo parameterInfo, ActionDescriptor actionDescriptor)
在这里还有一个小问题,就是当我们访问一个Action若干次的时候,是否每一次都实例化这个Action所需要的ParameterDescriptor对象。当然不用,我们想到了缓存,在ReflectedActionDescriptor类中有一个ParameterDescriptor[]类型的_parametersCache字段,它的作用就是缓存ParameterDescriptor对象,感兴趣的朋友可以看一下。
4.IModelBinder接口:这个接口是实现ModelBinder的关键,我们熟知的DefaultModelBinder类就是继承了这个接口,当我们实现自己的ModelBinder时,也要实现这个接口,这个接口只有一个方法,就是BindModel,它接收ModelBindingContext和ControllerContext为参数,提取信息,并实例一个Action想要的类型的实例。
在整个ModelBinder过程中,我们上面说了我概括出三个过程,获取IModelBinder过程是第二个过程,那么到底是如何获取到IModelBinder对象的呢?是通过ParameterDescriptor和ModelBinderDictionary对象来实现的,为什么说是这两个呢?因为当我们使用DefaultModelBinder时,是ModelBinderDictionary为我们挑选的IModelBinder,而使用我们自己定义的ModelBinder时,是ParameterDescriptor对象为我们挑选的ModelBinder,其实是ReflectedParameterDescriptor调用ReflectedParameterBindingInfo的Binder属性,Binder属性又调用静态类ModelBinders,ModelBinders类继而通过反射获取到我们自定义的继承自CustomModelBinderAttribute类的那个ModelBinder。感觉乱的,就看一下mvc源代码便一目了然。
那么怎样决定让那一个给我们挑选IModelBinder呢?我们使用时是这样的:
public ActionResult LogOn([ModelBinder(typeof(UserInfoBinder))]UserInfo user,bool rememberme) public ActionResult LogOn(UserInfo user,bool rememberme)
第一行使用了一个自己的ModelBinder,第二行使用了DefaultModelBinder。下面是mvc的代码:
private IModelBinder GetModelBinder(ParameterDescriptor parameterDescriptor) { // look on the parameter itself, then look in the global table return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType); }
从上面的代码中,首先判断是否使用了自己的ModelBinder,如果为空,也就是第二行代码,那么就指定使用DefaultModelBinder。
5.ModelBindingContext类:我们知道IModelBinder接口的BindModel方法需要两个参数,这个便是其中之一,所以它储藏了很多有用的信息,这些信息就包括我们要得到的对象的实例化的一些必要的信息。
这个类并没有显式的构造函数,但是我们需要指定它的字段和属性:
ModelBindingContext bindingContext = new ModelBindingContext() { FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), ModelName = parameterName, ModelState = controllerContext.Controller.ViewData.ModelState, ModelType = parameterType, PropertyFilter = propertyFilter, ValueProvider = valueProvider };
有了这几个属性或字段,我们在实例化特定类型的对象时就简单的多了。下面说一下这几个属性的作用:
1.FallbackToEmptyPrefix就是否使用可变的前缀,我们知道在html页我们使用user.name而在Action的参数中我们的参数名是userinfo,那么这个属性就用的上了。
2.ModelName就是我们说的user
3.ModelState是我们绑定时的状态信息,比如我们绑定一个值时未成功,那么在ModelState里就有一个Error信息。
4.ModelType就是user的类型。
5.PropertyFilter是过滤掉一些字段,就是我们想不绑定一个值是用的。
通过上面的了解,我想您已经了解了ModelBinder的工作机制,下面我们在通过几个流程图进一步分析。
ModelBinder流程Top
ModelBinder整个的过程其实在上一个小节中已经弄明白了,为了更加清楚其中的细节,我把这个过程做成了一个流程图,可以更确切描述上面的意思。
总结Top
ModelBinder的实现过程其实是用了反射的原理,在看源代码的时候可能会被乱糟糟的类弄的晕头转向,只要抓住那几个主要的类,慢慢的理清楚它们的职责,理解整个过程,以及其它辅助类的设计也就能了解到了。希望对您有帮助。