mvc源码解读(18)-数据绑定组件ModelBinder之DefaultModelBinder
前面的文章中我们已经讲了三种的ModelBinder组件,在讲第四种ModelBinder组件之前,我想解释一下什么是ModelBinder,顾名思义就是模型绑定,就是浏览器发送Http请求时根据相关的数据创建对象的过程,比如我们每一次请求一个action的时候,创建这个action参数对象的过程就可以叫做模型绑定,因此我们可以认为ModelBinder就是生成匹配的action参数的值。
通过前面的文章介绍我们可以发现,mvc里面是有很多个ModelBinder,每一个ModelBinder可以绑定一个或是多个Model。当我们请求一个action的时候,会首先查找定义在action里面的参数并同时找到对应的负责每一个参数类型的ModelBinder,如果找不到的话,就会调用系统默认的ModelBinder。
DefaultModelBinder类里面有很多个方法,我们如何来跟踪里面的逻辑判断呢,这里有一个技巧,我们可以自己定义一个ModelBinder,让这个自定义的ModelBinder继承DefaultModelBinder,并重写里面的方法即可。我们来一一分析:
(1)绑定简单的数据类型:一般包括int,string.......这些简单的类型,如我们请求的Action为MyInt(int age),先来看看我们的view:
相对应的Action如下:
点击提交按钮之后,执行的是BindSimpleModel方法,并通过ConvertProviderResult方法直接将value赋值给了age参数:
internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult) { ............................................................. return model; } |
假如我们在文本框中输入12,按提交的时候,ConvertProviderResult方法里面执行结果如下:
当然如果是数组的话,int[]和string[]类型的绑定情况有些不一样,int[]类型的数组和int,string。。。。。一样都是通过ConvertProviderResult方法获取到参数的值,但是string[]参数里面的值是通过ValueProviderResult里面的RawValue获取到的,相关的demo如下:
MyString()定义如下:
在三个文本框中输入:老A,老B,老C,运行截图如下:
如果Action的参数是一下类型如:List<T>(其中T不是Model,而是简单的数据类型)等集合或是array数组的话,执行的都是BindSimpleModel方法,调用的对应的获取值得方法,细节不阐述。
(2)复杂类型:mvc框架对于复杂类型的数据绑定采用的是递归的方法。复杂类型的情况有很多种,我们一一来看:
(2.1):如果Action的参数形式如下:
就是直接将Model作为参数的话,将执行BindComplexModel绑定复杂模型方法,当然开始的时候如果模型元数据里面的ModelMetadata.Model为空的话,则首先要创建一个Model,随着执行BindComplexElementalModel方法:
BindComplexElementalModel(controllerContext, bindingContext, model); |
当然这里面会创建一个新的内部的绑定上下文对象,然后循环调用BindProperties绑定属性的方法:
private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext); foreach (PropertyDescriptor property in properties) { BindProperty(controllerContext, bindingContext, property); } } |
这里面红色大代码实际上执行的又是BindSimpleModel方法,循环调用该方法,知道Model的属性被赋值为止。
(2.2)如果Action的参数形式如下:
紧接着创建一个强类型的Model的View,如下截图所示(只是其中的一小部分):
运行之后会发现一个很坑爹的问题:注意看我们的运行时的断点情况:
也就是没有获取到相应的数据,关键在于ContainsPrefix这个方法,该方法的定义如下:
// 摘要: // 确定集合是否包含指定的前缀。 // // 参数: // prefix: // 要搜索的前缀。 // // 返回结果: // 如果集合包含指定的前缀,则为 true;否则为 false。 bool ContainsPrefix(string prefix); |
就是获取集合的指定的前缀,就比如说是复杂类型的参数,参数前面都是一般都是带有Model+属性的形式:Model.属性作为参数传递的,而我们view中并显示没有指定前缀,因此运行的最终的结果是:
我们可以看到userInfo参数为null,并没有成功将客户端的值绑定到action的参数中(注意:客户端输入的数据的图没有贴出来)。
既然如此我们改变一下策略,在view中我们定义如下:
就是我们手动定义它的前缀,这里应该注意到:1:当action的参数如果是数组或者IEumerable<T>的,例如 Action(List<UserInfo> UserInfo),系统在内部内部创建一个List,进行数组绑定。数组绑定的时候,对数组中的key有几个约束条件:
1.1:是以数字为index的,比如 [0].Name, [0].Age, [0].Gender,[1].Name, [1].Age, [0].Gender。这里的数字必须是从0开始而且是连续的,当然这个条件一般比较难满足的,因为在实际编码中我们不可能自己洗去写这个索引,我们当然可以通过循环遍历的方法来确定key的值。
我们再次运行一下程序,运行结果如下:
我们可以看到已经成功将数据绑定到action参数中。这里面我们应该注意到,和action(Model model)形式的参数绑定差不多,action(List<Model> model)绑定时会首先创建一个List<>,再循环遍历Model的属性并赋值,再将model放入List集合中返回给客户端。其他的只要是IEumerable<T>,或者IDictionary<k,t>参数绑定原理都是一样的,这里不做参数。
这里倒是有另外一个问题,如果一个Model中属性也是一个复杂类型的话,它的参数绑定又是如何的呢?
我们重新修改一下我们的Model:
Address也是一个实体,其中View如下:
运行效果如下:
遍历UserInfo所有属性的时候,如果有些属性本身也是复杂类型,则循环刚才所说的BindComplexModel方法,创建了Model执行后在执行BindSimpleModel,一步一步的赋值。
绑定复杂对象的时候,需要能够区分这个key对应的是哪个对象上的属性值。比如上面的例子中,name="Address.city"就表明,这是UserInfo类型的Address属性的City属性和Province属性,默认的ModelBinder是通过点号和中括号来区分的,点号分隔开的一段段称为prefix即前缀,有关前缀的作用和意义暂且不述~~~~~~~~~~