model的绑定体现在从当前请求提取相应的数据绑定到目标action方法的参数。通过前面的介绍我们知道action方法的参数通过parameterdescriptor来描述,parameterdescriptor的bindinginfo属性表示的parameterbindinginfo对象具有一个名为modelbinder的用于完成针对当前参数的model绑定。modelbinder可以看成是整个model绑定的核心,我们先来认识这个重要的组件。
目录
一、 modelbinder
二、custommodelbinderattribute与modelbinderattribute
三、modelbinders
四、modelbinderprovider
一、 modelbinder
用于进行model绑定的modelbinder对象实现了接口imodelbinder。如下面的代码片断所示,imodelbinder接口具有唯一的bindmodel方法用于实现针对某个参数的绑定操作,该方法的返回值表示的就是最终作为参数值的对象。
   1: public interface imodelbinder
   2: {
   3:     object bindmodel(controllercontext controllercontext, modelbindingcontext bindingcontext);
   4: }
imodelbinder的bindmodel方法接受两个参数,一个是表示当前的controller上下文,另一个是表示针对当前model绑定的上下文,通过类型modelbindingcontext表示。在controller初始化的时候,controller上下文已经被创建出来,所以我们只要能够针对当前的model绑定创建相应的modelbindingcontext,我们就能使用基于某个参数的modelbinder得到对应的参数值。关于modelbindingcontext的创建我们会在后续部分进行的单独介绍,我们先来介绍一下modelbinder的提供机制。
二、custommodelbinderattribute与modelbinderattribute
如果针对某个参数的parameterdescriptor具有相应的modelbinder,那么它会被优先选择用于针对该参数的model绑定,那么parameterdescriptor的modelbinder是如何来提供的呢?这是实际上设置一个具有如下定义的custommodelbinderattribute特性。抽象类custommodelbinderattribute定义了唯一的抽象方法getbinder用于获取相应的modelbinder对象。
   1: [attributeusage(attributetargets.parameter | attributetargets.interface | attributetargets.enum | attributetargets.struct
   2:     | attributetargets.class, allowmultiple=false, inherited=false)]
   3: public abstract class custommodelbinderattribute : attribute
   4: {
   5:     public abstract imodelbinder getbinder();
   6: }
在asp.net mvc应用接口中,custommodelbinderattribute具有一个具有如下定义的唯一继承类型modelbinderattribute。我们可以通过应用modelbinderattribute特性动态地选择用于model绑定的modelbinder类型。
   1: [attributeusage(attributetargets.parameter | attributetargets.interface |
   2:     attributetargets.enum | attributetargets.struct | attributetargets.class,
   3:     allowmultiple=false, inherited=false)]
   4: public sealed class modelbinderattribute : custommodelbinderattribute
   5: {  
   6:     public modelbinderattribute(type bindertype);
   7:     public override imodelbinder getbinder();
   8: 
   9:     public type bindertype { [compilergenerated] get;  }
  10: }
从应用在modelbinderattribute类型上的attributeusageattribute定义可以看出该特性不仅仅可以应用在参数上,也可以应用类型(接口、枚举、结构和类)上,这意味我们既可以将它应用在action方法的某个参数上,也可以将它应用在某个参数的类型上。但是parameterdescriptor只会解析应用在参数上的特性,所以应用在参数对象类型上的modelbinderattribute对它是无效的。
为了演示modelbinderattribute特性对parameterdescriptor的影响,我们来进行一个简单的实例演示。在一个通过visual studio的asp.net mvc项目模板创建的空web应用中定义了如下几个类型,其中foomodelbinder和barmodelbinder是显现了imodelbinder的自定义modelbinder类型,而foo、bar和baz是三个将被作为action方法参数的数据类型,其中bar上应用了modelbinderattribute特性并将modelbinder类型设置为barmodelbinder。
   1: public class foomodelbinder : imodelbinder
   2: {
   3:     public object bindmodel(controllercontext controllercontext, modelbindingcontext bindingcontext)
   4:     {
   5:         throw new notimplementedexception();
   6:     }
   7: }
   8: public class barmodelbinder : imodelbinder
   9: {
  10:     public object bindmodel(controllercontext controllercontext, modelbindingcontext bindingcontext)
  11:     {
  12:         throw new notimplementedexception();
  13:     }
  14: }
  15: 
  16: public class foo { }
  17: [modelbinder(typeof(barmodelbinder))]
  18: public class bar { }
  19: public class baz { }
然后再创建的默认homecontroller中定义如下两个action方法。dosomething方法具有三个参数,类型分别是foo、bar和baz,在第一个参数上应用了modelbinderattribute特性并将modelbinder类型设置为foomodelbinder。
   1: public class homecontroller : controller
   2: {
   3:     public void index()
   4:     {
   5:         controllerdescriptor controllerdescriptor = new reflectedcontrollerdescriptor(typeof(homecontroller));
   6:         actiondescriptor actiondescriptor = controllerdescriptor.findaction(controllercontext, “dosomething”);
   7:         imodelbinder foo = actiondescriptor.getparameters().first(p => p.parametername == “foo”).bindinginfo.binder;
   8:         imodelbinder bar = actiondescriptor.getparameters().first(p => p.parametername == “bar”).bindinginfo.binder;
   9:         imodelbinder baz = actiondescriptor.getparameters().first(p => p.parametername == “baz”).bindinginfo.binder;
  10: 
  11:         response.write(string.format(“foo: {0}<br/>”, null == foo? “n/a”: foo.gettype().name));
  12:         response.write(string.format(“bar: {0}<br/>”, null == bar ? “n/a” : bar.gettype().name));
  13:         response.write(string.format(“baz: {0}<br/>”, null == baz ? “n/a” : baz.gettype().name));
  14:     }
  15: 
  16:     public void dosomething([modelbinder(typeof(foomodelbinder))]foo foo,bar bar, bar baz)
  17:     {}               
  18: }
在默认的action方法index中,我们针对homecontroller类型的reflectedcontrollerdescriptor对象并获取到用于描述action方法dosomething的actiondescriptor对象。最后我们通过该actiondescriptor对象得到用于描述其三个参数的parameterdescriptor对象,并将其modelbinder类西国内呈现出来。当我们运行该程序的时候,会在中产生如下的输出结果,可以看出对于分别应用在参数和参数类型上的modelbinderattribute特性,只有前者会对parameterdescriptor的modelbinder的选择造成影响。
   1: foo: foomodelbinder
   2: bar: n/a
   3: baz: n/a

三、modelbinders
如果我们不曾通过modelbinderattribute特性为某个action方法参数的modelbinder类型进行显式定制,默认采用的model是通过静态类型modelbinders来提供的。如下面的代码片断所示,modelbinders具有一个静态只读属性binders,表示当前注册modelbinder列表,其类型为modelbinderdictionary。
   1: public static class modelbinders
   2: {  
   3:     public static modelbinderdictionary binders { get; }
   4: }
   5: 
   6: public class modelbinderdictionary :
   7:   idictionary<type, imodelbinder>,
   8:   icollection<keyvaluepair<type, imodelbinder>>,
   9:   ienumerable<keyvaluepair<type, imodelbinder>>,
  10:   ienumerable
  11: {   
  12:     //其他成员
  13:     public imodelbinder getbinder(type modeltype);
  14:    public virtual imodelbinder getbinder(type modeltype, bool fallbacktodefault);
  15: }
modelbinderdictionary是一个以数据类型(model类型)为key,modelbinder对象为value的字典,即它定义了针对某种数据类型的modelbinder。modelbinderdictionary具有两个getbinder方法重载用于获取针对某个数据类型的modelbinder,布尔类型的参数fallbacktodefault表示在数据类型不存在的时候是否采用默认的modelbinder,基于默认modelbinder的后备机制会在第一个getbinder方法重载中采用。在这里默认modelbinder类型为defaultmodelbinder。
在为某个参数获取相应的modelbinder的时候,如果对应的parameterdescriptor的modelbinder不存在,则通过modelbinders的静态属性binders表示获取到当前注册的modelbinder列表的modelbinderdictionary对象,并将参数类型作为参数调用其getbinder方法获取相应modelbinder对象。
我们根据modelbinder的提供机制对上面演示的实例进行相应的修改。我们在homeconroller中添加了一个checkmodelbinder方法,三个参数分别表示用于描述相应action方法的actiondescriptor对象、参数名称和类型。在该方法中我们先获取到用于描述制定参数的parameterdescriptor对象,如果它具有相应的modelbinder,则将具体的类型名称输出,否则输出通过modelbinders获取的针对参数类型的modelbinder类型。
   1: public class homecontroller : controller
   2: {
   3:     //其他成员
   4:     public void index()
   5:     {
   6:         controllerdescriptor controllerdescriptor = new reflectedcontrollerdescriptor(typeof(homecontroller));
   7:         actiondescriptor actiondescriptor = controllerdescriptor.findaction(controllercontext, “dosomething”);
   8: 
   9:         checkmodelbinder(actiondescriptor, “foo”, typeof(foo));
  10:         checkmodelbinder(actiondescriptor, “bar”, typeof(bar));
  11:         checkmodelbinder(actiondescriptor, “baz”, typeof(baz));          
  12:     }
  13: 
  14:     private void checkmodelbinder(actiondescriptor actiondescriptor, string parametername, type modeltype)
  15:     {
  16:         parameterdescriptor parameterdescriptor = actiondescriptor.getparameters().first(p=>p.parametername == parametername);
  17:         imodelbinder modelbinder = parameterdescriptor.bindinginfo.binder ?? modelbinders.binders.getbinder(modeltype);
  18:         response.write(string.format(“{0}: {1}<br/>”, parametername, null == modelbinder ? “n/a” : modelbinder.gettype().name));
  19:     }
  20: }
在index方法中,我们调用checkmodelbinder方法将action方法dosomething的三个参数对应的modelbinder类型呈现出来。当我们运行该程序的时候,在浏览器上会得到如下的输出结果,应用在类型bar上的barmodelbinder会用于针对参数bar的model绑定,而参数baz则会使用默认的defaultmodelbinder。
   1: foo: foomodelbinder
   2: bar: barmodelbinder
   3: baz: defaultmodelbinder
对于上面的这个例子,由于数据类型baz没有关联modelbinder注册到通过modelbinders的静态属性binders表示的全局modelbinder列表中,所以才导致dosomething的baz参数采用默认的defaultmodelbinder。如果我们实现针对数据类型baz进行了相应的modelbinder注册,那么被注册的modelbinder将会自动用于该类型参数的model绑定。同样是针对上面演示的这个实例,我们定义了如下一个实现了imodelbinder的bazmodelbinder。
   1: public class bazmodelbinder : imodelbinder
   2: {
   3:     public object bindmodel(controllercontext controllercontext, modelbindingcontext bindingcontext)
   4:     {
   5:         throw new notimplementedexception();
   6:     }
   7: }
现在我们希望使用这个bazmodelbinder用于针对所有类型为bar的参数的model绑定,那么我们可以通过global.asax在应用启动的时候进行如下的modelbinder注册。
   1: public class mapplication : system.web.httpapplication
   2: {
   3:     //其他成员
   4:     protected void application_start()
   5:     {
   6:         //其他操作
   7:         modelbinders.binders.add(typeof(baz), new bazmodelbinder());
   8:     }
   9: }
再次运行我们的程序,在浏览器中会得到如下的输出结果,从中可以清楚地看出我们注册的bazmodelbinder并用于baz参数的model绑定。
   1: foo: foomodelbinder
   2: bar: barmodelbinder
   3: baz: bazmodelbinder

四、modelbinderprovider
asp.net mvc的model绑定系统还涉及到另一个重要的组件modelbinderprovider。顾名思义,modelbinderprovider专门用于提供相应的modelbinder对象,它们均实现了imodelbinderprovider面的代码片断所示,imodelbinderprovider接口定义了唯一的getbinder方法用于根据数据类型获取相应的modelbinder对象。不过在asp.net mvc现有的应用编程接口中并没有定义任何一个实现该接口的modelbinderprovider类型。
   1: public interface imodelbinderprovider
   2: {   
   3:     imodelbinder getbinder(type modeltype);
   4: }
我们可以利用modelbinderproviders为应用注册一组modelbinderprovider对象为某个数据类型提供相应的modelbinder。如下面的代码片断所示,静态类型modelbinderproviders具有一个静态只读属性binderproviders,其类型modelbinderprovidercollection实际上是一个型modelbinderprovider的集合,该集合表示针对当前应用的modelbinderprovider列表。
   1: public static class modelbinderproviders
   2: {   
   3:     public static modelbinderprovidercollection binderproviders { get; }
   4: }
   5: 
   6: public sealed class modelbinderprovidercollection : collection<imodelbinderprovider>
   7: {
   8:     //省略成员
   9: }
通过modelbinderproviders的静态属性binderproviders表示的modelbinderprovider列表最终被modelbinderdictionary使用。如下面的代码片断所示,modelbinderdictionary除了具有一个表示基于数据类型的modelbinder字典(_innerdictionary字段)和一个默认modelbinder(_defaultbinder)之外,还具有一个modelbinderprovider列表(_modelbinderproviders字段)。
   1: public class modelbinderdictionary
   2: {
   3:     //其他成员
   4:     private imodelbinder _defaultbinder;
   5:     private readonly dictionary<type, imodelbinder> _innerdictionary;
   6:     private modelbinderprovidercollection _modelbinderproviders;  
   7: }
当modelbinderdictionary被创建的时候,通过modelbinderproviders的静态属性binderproviders表示的modelbinderprovider列表会用于初始化_modelbinderproviders字段。围绕着modelbinder的model绑定系统中的核心组件之间的关系基本上可以通过下图所示的uml来表示。

 

当我们调用getbinder或者指定数据类型对应的modelbinder时,_innerdictionary字段表示的modelbinder字典会被优先选择。如果数据类型在该字典中找不到,则选择使用通过_modelbinderproviders字段表示的modelbinderprovider列表进行modelbinder的提供。只有在两种modelbinder提供方式均失败的情况下才会选择通过_innerdictionary字段表示的默认modelbinder。也就是说,如果我们想为某个数据类型定制某种类型的modelbinder,按照选择优先级具有如下几种方式供我们选择:
• 将modelbinderattribute应用在action方法的相应参数上并指定相应的modelbinder类型,或者在参数上应用一个自定义的custommodelbinderattribute特性。
• 将modelbinderattribute应用在数据类型上并制定相应的modelbinder类型,或者在数据类型上应用一个自定义的custommodelbinderattribute特性。
• 通过modelbinders的静态属性binders实现针对基于某种数据类型的modelbinder注册。
• 自定义modelbinderprovider实现基于某个数据类型的modelbinder提供机制,并通过注册当通过modelbinderproviders的静态属性binderproviders表示的modelbinderprovider列表中。
前面三种方式的modelbinder提供机制我们已经通过实例演示过了,现在我们来演示基于自定义modelbinderprovider的modelbinder提供机制。在前面的例子中我们为foo、bar和baz这三种数据类型创建了相应的modelbinder(foomodelbinder、barmodelbinder和bazmodelbinder),现在我们创建如下一个自定义的modelbinderprovider将两种(数据类型和modelbinder对象)进行关联。
   1: public class mymodelbinderprovider : imodelbinderprovider
   2: {
   3:     public imodelbinder getbinder(type modeltype)
   4:     {
   5:         if (modeltype == typeof(foo))
   6:         {
   7:             return new foomodelbinder();
   8:         }
   9:         if (modeltype == typeof(bar))
  10:         {
  11:             return new bazmodelbinder();
  12:         }
  13:         if (modeltype == typeof(baz))
  14:         {
  15:             return new bazmodelbinder();
  16:         }
  17:         return null;
  18:     }
  19: }
现在我们需要通过利用global.asax通过如下的方式在应用启动时将一个我们自定义的mymodelbinderprovider注册到通过modelbinderproviders的静态属性binderproviders表示的modelbinderprovider列表中。
   1: public class mvcapplication : system.web.httpapplication
   2: {
   3:     //其他成员
   4:     protected void application_start()
   5:     {
   6:         //其他操作
   7:        modelbinderproviders.binderproviders.add(new mymodelbinderprovider());
   8:     }
   9: }
由于mymodelbinderprovider实现了针对foo、bar和baz三种数据类型的modelbinder的提供,所以我们可以将应用在action方法参数和数据类型上的modelbinderattribute特性删除。再次运行我们的程序,会在浏览器中得到如下的输出结果,从中可以看到dosomething方法的三个参数此时采用了我们期望的modelbinder类型。
   1: foo: foomodelbinder
   2: bar: barmodelbinder
   3: baz: bazmodelbinder

 

摘自 artech