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

时间:2023-03-09 03:42:18
asp.net MVC 4.0 Controller回顾——ModelBinding实现过程

以DefaultModelBinder为例

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

 public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
bool flag = false;
if (!string.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
{
.......
}
if (!flag)
{
.......
if (valueProviderResult != null)
{
return this.BindSimpleModel(controllerContext, bindingContext, valueProviderResult);
}
}
if (!bindingContext.ModelMetadata.IsComplexType)
{
return null;
}
return this.BindComplexModel(controllerContext, bindingContext);
}
 

简单类型

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

 internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult)
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue))
{
return valueProviderResult.RawValue;
}
if (bindingContext.ModelType != typeof(string))
{
if (bindingContext.ModelType.IsArray)
{
return ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
}
Type type = TypeHelpers.ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>));
if (type != null)
{
object o = this.CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
Type collectionType = type.GetGenericArguments()[];
Type destinationType = collectionType.MakeArrayType();
object newContents = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, destinationType);
if (typeof(ICollection<>).MakeGenericType(new Type[] { collectionType }).IsInstanceOfType(o))
{
CollectionHelpers.ReplaceCollection(collectionType, o, newContents);
}
return o;
}
}
return ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
}
 

复杂类型

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

 internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
...
this.BindComplexElementalModel(controllerContext, bindingContext, model);
...
} internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model)
{
ModelBindingContext context = this.CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);
if (this.OnModelUpdating(controllerContext, context))
{
this.BindProperties(controllerContext, context);
this.OnModelUpdated(controllerContext, context);
}
}

遍历属性描述进行绑定

 private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
foreach (PropertyDescriptor descriptor in this.GetFilteredModelProperties(controllerContext, bindingContext))
{
this.BindProperty(controllerContext, bindingContext, descriptor);
}
}

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

 protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
string prefix = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
if (bindingContext.ValueProvider.ContainsPrefix(prefix))
{
IModelBinder propertyBinder = this.Binders.GetBinder(propertyDescriptor.PropertyType);
object obj2 = propertyDescriptor.GetValue(bindingContext.Model);
ModelMetadata metadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
metadata.Model = obj2;
ModelBindingContext context = new ModelBindingContext {
ModelMetadata = metadata,
ModelName = prefix,
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
object obj3 = this.GetPropertyValue(controllerContext, context, propertyDescriptor, propertyBinder);
......
}
 

集合类型、数组类型

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是否和参数类型一致,如果一致直接返回,不一致转换为参数类型返回

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

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

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

 internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
......
Type type7 = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>));
if (type7 != null)
{
Type type8 = type7.GetGenericArguments()[];
if (typeof(ICollection<>).MakeGenericType(new Type[] { type8 }).IsInstanceOfType(model))
{
ModelBindingContext context6 = new ModelBindingContext();
if (func2 == null)
{
func2 = () => model;
}
context6.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(func2, modelType);
context6.ModelName = bindingContext.ModelName;
context6.ModelState = bindingContext.ModelState;
context6.PropertyFilter = bindingContext.PropertyFilter;
context6.ValueProvider = bindingContext.ValueProvider;
ModelBindingContext context5 = context6;
return this.UpdateCollection(controllerContext, context5, type8);
}
}
......
}

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 == )
{
return null;
}
object model = bindingContext.Model;
CollectionHelpers.ReplaceCollection(elementType, model, newContents);
return model;
}

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

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

可以看到匹配项依次是 "",[索引],[索引].字段名,因为查找的时候是按照这个顺序来进行的,如果有[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的隐藏框代表索引项,下面再是具体的这个索引下面的数据项

 <input name="index" type="hidden" value="first" />
<input name="index" type="hidden" value="second" /> <input name="[first].UserName" type="text" value="" />
<input name="[first].Password" type="password" value="" />
<input name="[second].UserName" type="text" value="" />
<input name="[second].Password" type="password" value="" />

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

 [HttpPost]
public ActionResult LogOn(List<LogOnModel> UserName)
{
return View();
}

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

NameValueCollectionValueProvider类获取key值

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

 

字典类型

 internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
......
Type type4 = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<,>));
if (type4 != null)
{
Type[] genericArguments = type4.GetGenericArguments();
Type keyType = genericArguments[];
Type valueType = genericArguments[];
ModelBindingContext context4 = new ModelBindingContext();
if (modelAccessor == null)
{
modelAccessor = () => model;
}
context4.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(modelAccessor, modelType);
context4.ModelName = bindingContext.ModelName;
context4.ModelState = bindingContext.ModelState;
context4.PropertyFilter = bindingContext.PropertyFilter;
context4.ValueProvider = bindingContext.ValueProvider;
ModelBindingContext context3 = context4;
return this.UpdateDictionary(controllerContext, context3, keyType, valueType);
}
......
}

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

 internal object UpdateDictionary(ControllerContext controllerContext, ModelBindingContext bindingContext, Type keyType, Type valueType)
{
bool flag;
IEnumerable<string> enumerable;
GetIndexes(bindingContext, out flag, out enumerable);
IModelBinder binder = this.Binders.GetBinder(keyType);
IModelBinder binder2 = this.Binders.GetBinder(valueType);
List<KeyValuePair<object, object>> newContents = new List<KeyValuePair<object, object>>();
foreach (string str in enumerable)
{
string prefix = CreateSubIndexName(bindingContext.ModelName, str);
string str3 = CreateSubPropertyName(prefix, "key");
string str4 = CreateSubPropertyName(prefix, "value");
if (!bindingContext.ValueProvider.ContainsPrefix(str3) || !bindingContext.ValueProvider.ContainsPrefix(str4))
{
if (!flag)
{
continue;
}
break;
}
ModelBindingContext context = new ModelBindingContext {
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, keyType),
ModelName = str3,
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
object obj2 = binder.BindModel(controllerContext, context);
AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, str3, keyType, obj2);
if (keyType.IsInstanceOfType(obj2))
{
ModelBindingContext context2 = new ModelBindingContext {
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, valueType),
ModelName = str4,
ModelState = bindingContext.ModelState,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object obj3 = binder2.BindModel(controllerContext, context2);
AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, str4, valueType, obj3);
KeyValuePair<object, object> item = new KeyValuePair<object, object>(obj2, obj3);
newContents.Add(item);
}
}
if (newContents.Count == )
{
return null;
}
object model = bindingContext.Model;
CollectionHelpers.ReplaceDictionary(keyType, valueType, model, newContents);
return model;
}

字典类型View视图代码

 <input name="[0].key" type="text" value="" />
<input name="[0].value.UserName" type="text" value="" />
<input name="[0].value.Password" type="password" />
<input name="[0].value.RememberMe" type="checkbox" value="true" /> <input name="[1].key" type="text" value="" />
<input name="[1].value.UserName" type="text" value="" />
<input name="[1].value.Password" type="password" />
<input name="[1].value.RememberMe" type="checkbox" value="true" />

Controller

 [HttpPost]
public ActionResult LogOn(Dictionary<string, LogOnModel> model)
{
return View();
}