自动绑定的确是让人感到兴奋的特性,然而,为了让它可以在我们的项目中更好的工作,我们有必要深入了解如何更进一步细调该特性以及它是如何工作的.而本文正式即将揭开这个谜底.
为了更好的了解该特性,我们有必要到codeplex去下载一份asp.net mvc的源代码并分析之,在本文写作的时候,codeplex上已经放上了beta版的源码,如果想进一步了解的朋友可以下载并对照本文分析.
在beta版中,新增了自动绑定这一特性,并对绑定特性做了一定的修改
新增BindAttribute:自动绑定特性设置
修改DefaultModelBinder:自动绑定的实现部分
修改ControllerActionInvoker:绑定的调用入口
新增ModelBinderContext,封装绑定所需数据
新增BinderResult,封装绑定结果
其他不大重要的修改略过
我们一步步来分析绑定的执行过程,首先肯定在ControllerActionInvoker中,看到GetParameterValue方法:
这儿便是对每个参数都尝试调用ModelBinder来绑定参数,这儿的GetModelBinder方法和P5的一样,在我们自定义ModelBinder的情况下可以进行自定义绑定,然而在自动绑定的时候获取的则是DefaultModelBinder,然后在GetPropertyFilter方法中通过查阅BindAttribute来获取关于绑定的设置.最后对数据进行绑定.
关键的,我们需要对DefaultModelBinder进行分析,然而在此之前,还有一个类也是需要我们仔细看看的,那就是BindAttribute,该特性是用来修饰参数的,它有4个重要的属性:Include,Exclude,Prefix和一个方法:IsPropertyAllowed,分别用来设定:绑定的字段,不绑定的字段,参数前缀和判断给定的字段是否设定运行绑定,且该方法会作为一个Predicate<string>委托封入ModelBinderContext传入BindModel方法.
现在来讨论使用默认绑定的情况,首先给出DefaultModelBinder的所有方法:
分别简介下这些方法的作用:
BindModel:对外的调用接口,根据传入的ModelBinderContext绑定值
BindModelCore:绑定自定义类型,自定义类型数组或者自定义类型字典
BindProperty:绑定某个指定的属性,(此处是一个递归调用,仍然调用DefaultModelBinder进行属性的绑定,也就是说,理论上DefaultModelBinder可以对任意深度的属性进行绑定)
ConvertSimpleArrayType,ConvertSimpleType,用来做类型转换,一个转换数组一个转换普通类型
CreateArray:创建一个数组对象
CreateModel:创建一个普通对象
CreateSubIndexName:创建子索引名,命名方式为prefix[indexName]
CreateSubPropertyName:创建子属性名,命名方式为prefix.propertyName
GetBinder:获取modelType的Binder对象
GetElementType:获取一个类型的ElementType
GetSimeType:对给定的ModelBinderContext进行简单值绑定(也就是调用该方法时假定获取的数据是简单类型)
IsCollectionInserface:判断是否是数组类型
IsDictinaryInterface:判断是否是字典类型
IsSimpleType:是否是简单类型(在这儿判断是否是值类型或者string)
TryUpdateSimpleCollection:尝试绑定一个简单数组(即绑定Collection或者Collection<T>,且T能通过IsSimpleType)
UpdateCollection:绑定一个数组,该数组一般为Collection<T>,且T是自定义类型
UpdateDictionary:绑定一个字典类型
通过以上方法的浏览,我们发现,DefaultModelBinder可以进行绑定的数据很多,包括简单类型(值类型和string).自定义类型,数组,字典,同时由于采用了递归调用,理论上可以绑定任意深度的数据,由于这儿绑定的调用比较复杂,且本人表达能力有限,本文将不再详细讲述绑定的工作流程,下面将总结下绑定的规则:
默认绑定采用反射的方式将数据绑定到所需对象上
绑定参数有可选特性BindAttribute定义
BindAttribute中可以手动设置允许绑定的属性或者不允许绑定的属性,url取参前缀
默认情况下所有属性都进行绑定
DefaultModelBinder采用递归对所有需要绑定的参数进行绑定
普通参数的命名方式为:prefix.protertyName,且默认情况下prefix为类型名.如对象MyData拥有类型为string的属性Name,那么在ValueProvider中取值的名称应该为mydata.name,命名不分大小写
简单数组命名方式为prefix.protertyName,其中proertyName为数组名,可以存在多个
对于自定义类型数组和字典,绑定必须提供如下参数:
index-必须提供一个或多个,指示绑定的子元素名,可存在多个
数组必须提供一个或者多个以protertyName[indexName].subProtertyName形式结尾的表单数据,其中protertyName为数组名,indexName为index中定义过的名称,subProteryName为自定义类型的属性名.
字典必须提供一个或者多个protertyName[indexName].key和protertyName[indexName].value的表单数据,其中protertyName为字典名称,indexName为index中定义过的名称,key代表字典中的key绑定,value代表字典中的value绑定,如果key或者value为非简单类型,表单的定义继续参照前面
对于以上绑定,如果子级属性为复杂类型,可以依次按照此规则命名表单数据
下面我们通过例子来分别描述上面的规则:
在体验篇中,我们看到对article的表单命名为article.title,article.content等,这就是根据第6条,父类和属性用.来间隔,对简单数据的命名,article.tags,多个同名表单代表多个数族元素,参考第7条,在这儿,如果在article参数中显式定义了prefix,则需要对应对表单名进行修改.对于属性为自定义类型的,继续使用.间隔,比如上文中的article.advancearticle.today等,此处参考第7,9条,这些部分的例子可以参考第一篇,本篇中将不再说明.
本篇中将重点介绍自定义类型数组,字典以及多级属性绑定,下面展示如何绑定一个ICollection<T>,T为自定义类型,比如在AdvanceArticle中添加一个属性Reads,这是一个自定义类型,包含Name和Source,实例代码如下;
然后根据规则在表单aspx中加入:
这儿给这个数组传入了两个元素,分别对应article.reads.index中的0和aa,下面的article.reads[0].name和article.reads[0].source表示第一个元素的子属性,依次类推.如果您在应用中需要在数组中添加更多元素,可以采用js动态添加表单域的方法,不过每次添加必须对应添加index和对应的子属性表单.
然后是绑定Dictinary<TKey,TValue>,我们在AdvanceArticle中继续加入新属性
然后在aspx中加入表单:
和数组类似的,需要定义article.source.index,此处也显式定义了两个字典元素,分别是user和vip(注意,这儿的user和vip并不代表字典key,字典key是需要从表单绑定的),然后分别定义article.source[user].key和article.source[user].key等.如果有更多的元素,也需要按照此规则加入.
同样的,这儿的key和value也不局限于简单类型,如果key和value是自定义类型,则可以按照前面的规则继续向下定义.
最后需要说明几点: By Leven
2008-10-22
1. 绑定取值不限于表单域,在Beta中加入了ValueProvider,只要是能在ValueProvider中能取到的值都能绑定.
2. 自动绑定是基于反射,如果你嫌效率不够高,请采用自定义ModelBinder的方式对特定的类型进行特定的绑定已提升效率
3. 自动绑是单向的,如果想实现在route中能接受该参数,可以采用重写绑定对象的ToString方法输出对应QueryString的方式,也就是说,如果想在调用Url.Action(“xxx”,new { article= myArticle })能正确传入传出参数,可以重写Article的ToString方法,在提供一个类似article.title=xxx&article.content=xxx的字符串格式提供一个帮助类,将将数据分别放入ValueProvider,而调用Url方法时采用类似Url.Action(“xxx”,article.ToRouteValue())的方法
本文作者: