asp.net MVC 4.0 Controller回顾——ModelBinding实现过程

以DefaultModelBinder为例

为简单模型绑定(BindSimpleModel)和复杂模型绑定(BindComplexModel)

 1 public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
 2         {
 3             if (bindingContext == null)
 4             {
 5                 throw new ArgumentNullException("bindingContext");
 6             }
 7             bool flag = false;
 8             if (!string.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
 9             {
10                 .......
11             }
12             if (!flag)
13             {
14                 .......
15                 if (valueProviderResult != null)
16                 {
17                     return this.BindSimpleModel(controllerContext, bindingContext, valueProviderResult);
18                 }
19             }
20             if (!bindingContext.ModelMetadata.IsComplexType)
21             {
22                 return null;
23             }
24             return this.BindComplexModel(controllerContext, bindingContext);
25         }
 

简单类型

简单类型就是直接通过ValueProviderResult valueProviderResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation);获取Result,直接通过BindSimpleModel返回RawValue值。

 1 internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult)
 2         {
 3             bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
 4             if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue))
 5             {
 6                 return valueProviderResult.RawValue;
 7             }
 8             if (bindingContext.ModelType != typeof(string))
 9             {
10                 if (bindingContext.ModelType.IsArray)
11                 {
12                     return ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
13                 }
14                 Type type = TypeHelpers.ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>));
15                 if (type != null)
16                 {
17                     object o = this.CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
18                     Type collectionType = type.GetGenericArguments()[0];
19                     Type destinationType = collectionType.MakeArrayType();
20                     object newContents = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, destinationType);
21                     if (typeof(ICollection<>).MakeGenericType(new Type[] { collectionType }).IsInstanceOfType(o))
22                     {
23                         CollectionHelpers.ReplaceCollection(collectionType, o, newContents);
24                     }
25                     return o;
26                 }
27             }
28             return ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
29         }
 

复杂类型


绑定类型为复杂类型是绑定属性

 1 internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
 2 {
 3    ...
 4    this.BindComplexElementalModel(controllerContext, bindingContext, model);
 5    ...
 6 }
 7 
 8 internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model)
 9 {
10     ModelBindingContext context = this.CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);
11     if (this.OnModelUpdating(controllerContext, context))
12     {
13         this.BindProperties(controllerContext, context);
14         this.OnModelUpdated(controllerContext, context);
15     }
16 }

遍历属性描述进行绑定

1 private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
2         {
3             foreach (PropertyDescriptor descriptor in this.GetFilteredModelProperties(controllerContext, bindingContext))
4             {
5                 this.BindProperty(controllerContext, bindingContext, descriptor);
6             }
7         }

这时又会把bindingContext.ModelName和propertyDescriptor.Name进行组合成为新的前缀进行值得获取,并且获取新的ModelBindingContext进行绑定

 1 protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
 2  {
 3             string prefix = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
 4             if (bindingContext.ValueProvider.ContainsPrefix(prefix))
 5             {
 6                 IModelBinder propertyBinder = this.Binders.GetBinder(propertyDescriptor.PropertyType);
 7                 object obj2 = propertyDescriptor.GetValue(bindingContext.Model);
 8                 ModelMetadata metadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
 9                 metadata.Model = obj2;
10                 ModelBindingContext context = new ModelBindingContext {
11                     ModelMetadata = metadata,
12                     ModelName = prefix,
13                     ModelState = bindingContext.ModelState,
14                     ValueProvider = bindingContext.ValueProvider
15                 };
16                 object obj3 = this.GetPropertyValue(controllerContext, context, propertyDescriptor, propertyBinder);
17   ......
18 }
 

集合类型、数组类型

1.相同数据项的数组绑定

作为数据源的NameValueCollection没有对key做唯一性约束,当参数类型为简单数据类型的数组或集合时,同一个key将对应多个值,这时key获取到的ValueProviderResult就要转换为数组或集合。

1 <input name="UserName" type="text" value="" />
2 <input name="UserName" type="text" value="" />
3 <input name="Password" type="password" />
4 <input name="Password" type="password" />
5 <input name="RememberMe" type="checkbox" value="true" />
6 <input name="RememberMe" type="checkbox" value="true" />

参数名称必须为name,因为

if (!string.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))通过,直接把参数名称作为key来获取值

注:一般来说参数类型为简单类型时参数名称必须也name相同,因为是直接用参数名称作为key去匹配取值的,其他数据类型的参数名称可以随便取(简单类型的数组或集合除外)

1 public ActionResult LogOn(List<int> UserName) 2 { 3 return View(); 4 } 

在判断modelType是否和参数类型一致,如果一致直接返回,不一致转换为参数类型返回

2.整数和字符串索引的数组绑定

通过BindComplexModel方法TypeHelpers.ExtractGenericInterface()判断是否为数组和集合

 1 internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
 2 {
 3    ......
 4    Type type7 = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>));
 5             if (type7 != null)
 6             {
 7                 Type type8 = type7.GetGenericArguments()[0];
 8                 if (typeof(ICollection<>).MakeGenericType(new Type[] { type8 }).IsInstanceOfType(model))
 9                 {
10                     ModelBindingContext context6 = new ModelBindingContext();
11                     if (func2 == null)
12                     {
13                         func2 = () => model;
14                     }
15                     context6.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(func2, modelType);
16                     context6.ModelName = bindingContext.ModelName;
17                     context6.ModelState = bindingContext.ModelState;
18                     context6.PropertyFilter = bindingContext.PropertyFilter;
19                     context6.ValueProvider = bindingContext.ValueProvider;
20                     ModelBindingContext context5 = context6;
21                     return this.UpdateCollection(controllerContext, context5, type8);
22                 }
23             }
24    ......
25 }

GetIndexes()首先判断是否是有[Index]为前缀的值,如果有获取Name=Index的IEnumerable<string>集合(字符串索引集合),就根据字符串索引查找,如果没有就按照 [数字] 索引查找,再根据ModelName+[当前索引前缀],进行模型绑定,通过ValueProvider.ContainsPrefix(prefix)判断是否包含当前前缀的,重新获取elementType的ModelBindingContext进行模型绑定(elementType为集合类型,通过 type7.GetGenericArguments()[0]获取到

internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType)
        {
            bool flag;
            IEnumerable<string> enumerable;
            GetIndexes(bindingContext, out flag, out enumerable);
            IModelBinder binder = this.Binders.GetBinder(elementType);
            List<object> newContents = new List<object>();
            foreach (string str in enumerable)
            {
                string prefix = CreateSubIndexName(bindingContext.ModelName, str);
                if (!bindingContext.ValueProvider.ContainsPrefix(prefix))
                {
                    if (!flag)
                    {
                        continue;
                    }
                    break;
                }
                ModelBindingContext context = new ModelBindingContext {
                    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, elementType),
                    ModelName = prefix,
                    ModelState = bindingContext.ModelState,
                    PropertyFilter = bindingContext.PropertyFilter,
                    ValueProvider = bindingContext.ValueProvider
                };
                object obj2 = binder.BindModel(controllerContext, context);
                AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, prefix, elementType, obj2);
                newContents.Add(obj2);
            }
            if (newContents.Count == 0)
            {
                return null;
            }
            object model = bindingContext.Model;
            CollectionHelpers.ReplaceCollection(elementType, model, newContents);
            return model;
        }

注:查找的时候是按照 [索引],[索引].字段名进行检索的

 可以看到匹配项依次是 "",[索引],[索引].字段名,因为查找的时候是按照这个顺序来进行的,如果有[0]就代表有索引为0的项的数据,就可以进行后续的当前项的模型绑定

View代码 整数索引

 1 @using System.Collections.ObjectModel
 2 @model ObservableCollection<MvcSource.Models.LogOnModel>
 3 
 4 @Html.LabelFor(m => m[0].UserName)
 5 @Html.LabelFor(m => m[0].Password)
 6 @Html.CheckBoxFor(m => m[0].RememberMe)
 7 
 8 @Html.LabelFor(m => m[1].UserName)
 9 @Html.LabelFor(m => m[1].Password)
10 @Html.CheckBoxFor(m => m[1].RememberMe)

生成出来的HTML源码

1 <input name="[0].UserName" type="text" value="" />
2 <input name="[0].Password" type="password" />
3 <input name="[0].RememberMe" type="checkbox" value="true" />
4 
5 <input name="[1].UserName" type="text" value="" />
6 <input name="[1].Password" type="password" />
7 <input name="[1].RememberMe" type="checkbox" value="true" />

字符串索引,设置Name=index的隐藏框代表索引项,下面再是具体的这个索引下面的数据项

1 <input name="index" type="hidden" value="first" />
2 <input name="index" type="hidden" value="second" />
3  
4 <input name="[first].UserName" type="text" value="" />
5 <input name="[first].Password" type="password" value="" />
6 <input name="[second].UserName" type="text" value="" />
7 <input name="[second].Password" type="password" value="" />

Controller调用方法参数为List<T>

1 [HttpPost]
2 public ActionResult LogOn(List<LogOnModel> UserName)
3 {
4     return View();
5 }

上面分析UpdateCollection 时这时候模型绑定的前缀为 [0].UserName

NameValueCollectionValueProvider类获取key值

 

字典类型

 1 internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
 2 {
 3    ......
 4    Type type4 = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<,>));
 5             if (type4 != null)
 6             {
 7                 Type[] genericArguments = type4.GetGenericArguments();
 8                 Type keyType = genericArguments[0];
 9                 Type valueType = genericArguments[1];
10                 ModelBindingContext context4 = new ModelBindingContext();
11                 if (modelAccessor == null)
12                 {
13                     modelAccessor = () => model;
14                 }
15                 context4.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(modelAccessor, modelType);
16                 context4.ModelName = bindingContext.ModelName;
17                 context4.ModelState = bindingContext.ModelState;
18                 context4.PropertyFilter = bindingContext.PropertyFilter;
19                 context4.ValueProvider = bindingContext.ValueProvider;
20                 ModelBindingContext context3 = context4;
21                 return this.UpdateDictionary(controllerContext, context3, keyType, valueType);
22             }
23    ......
24 }

字典绑定同样是按照整数和字符串索引来来进行分组的,举例整数索引[0],字典的键的前缀为[0].key,值得前缀为[0].value,判断是否在数据源中有这些匹配项,如果有再进行后续的绑定操作,看到源码我们知道分别获取了键的ModelBindingContext和值得ModelBindingContext分别进行键和值的参数获取,binder.BindModel(controllerContext, context);为MVC中所有数据类型的获取参数的具体执行者,通过传入一个ModelBindingContext来执行的

 1 internal object UpdateDictionary(ControllerContext controllerContext, ModelBindingContext bindingContext, Type keyType, Type valueType)
 2         {
 3             bool flag;
 4             IEnumerable<string> enumerable;
 5             GetIndexes(bindingContext, out flag, out enumerable);
 6             IModelBinder binder = this.Binders.GetBinder(keyType);
 7             IModelBinder binder2 = this.Binders.GetBinder(valueType);
 8             List<KeyValuePair<object, object>> newContents = new List<KeyValuePair<object, object>>();
 9             foreach (string str in enumerable)
10             {
11                 string prefix = CreateSubIndexName(bindingContext.ModelName, str);
12                 string str3 = CreateSubPropertyName(prefix, "key");
13                 string str4 = CreateSubPropertyName(prefix, "value");
14                 if (!bindingContext.ValueProvider.ContainsPrefix(str3) || !bindingContext.ValueProvider.ContainsPrefix(str4))
15                 {
16                     if (!flag)
17                     {
18                         continue;
19                     }
20                     break;
21                 }
22                 ModelBindingContext context = new ModelBindingContext {
23                     ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, keyType),
24                     ModelName = str3,
25                     ModelState = bindingContext.ModelState,
26                     ValueProvider = bindingContext.ValueProvider
27                 };
28                 object obj2 = binder.BindModel(controllerContext, context);
29                 AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, str3, keyType, obj2);
30                 if (keyType.IsInstanceOfType(obj2))
31                 {
32                     ModelBindingContext context2 = new ModelBindingContext {
33                         ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, valueType),
34                         ModelName = str4,
35                         ModelState = bindingContext.ModelState,
36                         PropertyFilter = bindingContext.PropertyFilter,
37                         ValueProvider = bindingContext.ValueProvider
38                     };
39                     object obj3 = binder2.BindModel(controllerContext, context2);
40                     AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, str4, valueType, obj3);
41                     KeyValuePair<object, object> item = new KeyValuePair<object, object>(obj2, obj3);
42                     newContents.Add(item);
43                 }
44             }
45             if (newContents.Count == 0)
46             {
47                 return null;
48             }
49             object model = bindingContext.Model;
50             CollectionHelpers.ReplaceDictionary(keyType, valueType, model, newContents);
51             return model;
52         }

字典类型View视图代码

1 <input name="[0].key" type="text" value="" />
2 <input name="[0].value.UserName" type="text" value="" />
3 <input name="[0].value.Password" type="password" />
4 <input name="[0].value.RememberMe" type="checkbox" value="true" />
5  
6 <input name="[1].key" type="text" value="" />
7 <input name="[1].value.UserName" type="text" value="" />
8 <input name="[1].value.Password" type="password" />
9 <input name="[1].value.RememberMe" type="checkbox" value="true" />

Controller

1 [HttpPost]
2 public ActionResult LogOn(Dictionary<string, LogOnModel> model)
3 {
4    return View();
5 }
原文地址:https://www.cnblogs.com/raohuagang/p/3965556.html