[转载]深入分析 ASP.NET Mvc 1.0 – ModelBinder – Never give up – 博客园.
在前一篇文章已经讲叙Controller.Execute(…)方法的执行流程中会调用 ControllerActionInvoker类的InvokeAction(ControllerContext controllerContext, string actionName)方法, 在InvokeAction(…)方法内又调用了GetParameterValues(…)方法,这个方法为Action中的每个参数赋值,追踪到 GetParameterValues(…)方法内部会发现其实每个参数的值是由GetParameterValue(…)返回的,观察 GetParameterValue(…)方法的源码:
protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) {
// collect all of the necessary binding properties
Type parameterType = parameterDescriptor.ParameterType;
IModelBinder binder = GetModelBinder(parameterDescriptor);
IDictionary<string, ValueProviderResult> valueProvider = controllerContext.Controller.ValueProvider;
string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);
// finally, call into the binder
ModelBindingContext bindingContext = new ModelBindingContext() {
FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
ModelName = parameterName,
ModelState = controllerContext.Controller.ViewData.ModelState,
ModelType = parameterType,
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
object result = binder.BindModel(controllerContext, bindingContext);
return result;
}
代码的逻辑分为:
- 获取继承了IModelBinder接口的对象
- 执行IModelBinder对象的BindModel(…)方法并返回Action的参数值
继承IModelBinder接口的对象有三个类:DefaultModelBinder, FormCollectionModelBinder和HttpPostedFileBaseModelBinder
在GetParameterValue(…)方法中可以看到由GetModelBinder(parameterDescriptor)负责返回 IModelBinder对象,但他是如何确定返回哪个ModeBinder的呢?
- 当Action的参数类型为FormCollection时,GetParameterValue(…)方法返回 FormCollectoinModelBinder对象
- 当Action的参数类型为HttpPostedFileBase时,GetParameterValue(…)方法返回 HttpPostedFileBaseModelBinder对象
- 当Action的参数类型除了FormCollection和HttpPostedFileBase外(int, string, boolean或强类型),GetParameterValue(…)方法返回DefaultModelBinder对 象,DefaultModelBinder也是最常用的ModelBinder
来深入到GetModelBinder(…)方法的内部
private IModelBinder GetModelBinder(ParameterDescriptor parameterDescriptor) {
// look on the parameter itself, then look in the global table
return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);
}
通常情况下IModelBinder都是由 Binders.GetBinder(parameterDescriptor.ParameterType)返回, Binders属性调用ModelBinders.Binders返回ModelBinderDictionary的静态实例,在调用 ModelBinders.Binders属性返回ModelBinderDictionary对象之前先初始化 ModelBinderDictionary对象并将HttpPostedFileBaseModelBinder保存到 ModelBinderDictionary对象中(代码如下),最后其实是调用ModelBinderDictionary的GetBinder()方 法返回的IModelBinder对象。
public static class ModelBinders {
private static readonly ModelBinderDictionary _binders = CreateDefaultBinderDictionary();
public static ModelBinderDictionary Binders {
get {
return _binders;
}
}
private static ModelBinderDictionary CreateDefaultBinderDictionary() {
// We can't add a binder to the HttpPostedFileBase type as an attribute, so we'll just
// prepopulate the dictionary as a convenience to users.
ModelBinderDictionary binders = new ModelBinderDictionary() {
{ typeof(HttpPostedFileBase), new HttpPostedFileBaseModelBinder() }
};
return binders;
}
}
ModelBinderDictionary的GetBinder()方法
public IModelBinder GetBinder(Type modelType) {
return GetBinder(modelType, true /* fallbackToDefault */);
}
public virtual IModelBinder GetBinder(Type modelType, bool fallbackToDefault) {
if (modelType == null) {
throw new ArgumentNullException("modelType");
}
return GetBinder(modelType, (fallbackToDefault) ? DefaultBinder : null);
}
private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder) {
// Try to look up a binder for this type. We use this order of precedence:
// 1. Binder registered in the global table
// 2. Binder attribute defined on the type
// 3. Supplied fallback binder
IModelBinder binder;
if (_innerDictionary.TryGetValue(modelType, out binder)) {
return binder;
}
binder = ModelBinders.GetBinderFromAttributes(modelType,
() => String.Format(CultureInfo.CurrentUICulture, MvcResources.ModelBinderDictionary_MultipleAttributes, modelType.FullName));
return binder ?? fallbackBinder;
}
在签名为private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder) 的方法中会最返回IModelBinder对象,并且可以看出如果Action的参为类型为HttpPostedFileBase时,在 _innerDictionary.TryGetValue(modelType, out binder) 时就可能找到这个IModelBinder并返回;如果参数的类型为FormCollection 时,ModelBinders.GetBinderFromAttributes()会返回FormControllerModelBinder否则会返 回fallbackBinder也主是DefaultModelBinder对象
来看一个DefaultModelBinder对象的BindModel(…)方法的代码:
public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
if (bindingContext == null) {
throw new ArgumentNullException("bindingContext");
}
bool performedFallback = false;
if (!String.IsNullOrEmpty(bindingContext.ModelName) && !DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, bindingContext.ModelName)) {
// We couldn't find any entry that began with the prefix. If this is the top-level element, fall back
// to the empty prefix.
if (bindingContext.FallbackToEmptyPrefix) {
bindingContext = new ModelBindingContext() {
Model = bindingContext.Model,
ModelState = bindingContext.ModelState,
ModelType = bindingContext.ModelType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
performedFallback = true;
}
else {
return null;
}
}
// Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string))
// or by seeing if a value in the request exactly matches the name of the model we're binding.
// Complex type = everything else.
if (!performedFallback) {
ValueProviderResult vpResult;
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out vpResult);
if (vpResult != null) {
return BindSimpleModel(controllerContext, bindingContext, vpResult);
}
}
if (TypeDescriptor.GetConverter(bindingContext.ModelType).CanConvertFrom(typeof(string))) {
return null;
}
return BindComplexModel(controllerContext, bindingContext);
}
if (!String.IsNullOrEmpty(bindingContext.ModelName) && !DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, bindingContext.ModelName)) 查找指定的参数名是否包含在ValueProvider中,有两种情况:
- 在ValueProvider找到指定的参数名: 一般都是简单类型(int, boolean, string)或数组(页面中相同name的元素会以数组的形式赋给指定的参数)
- 没有在ValueProvider中找到指定的参数名:通常情况下都是strongly-typed类型,因为他的参数名不能与页面中的name 属性相同,否则会有异常被抛出;还有一种情况就是页面中元素的name属性与Action的参数名不一致,这时如果参数不能接收null类型就会有异常抛 出 。
接着上面的if语句,如果没有找到指定的参数并且 ModelBindingContext.FallbackToEmptyPrefix==true,那么就重新创建一个 ModelBindingContext对象,并设置performedFallback = true。
接下来的代码分两步执行:
- BindSimpleModel(controllerContext, bindingContext, vpResult): 为简单对象返回参数值
- BindComplexModel(controllerContext, bindingContext): 返 回complex对象(这里我理解为strongly-typed)的参数值
1. 首先看看BindSimpleModel(…)方法的过程:
internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult) {
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
// if the value provider returns an instance of the requested data type, we can just short-circuit
// the evaluation and return that instance
if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) {
return valueProviderResult.RawValue;
}
// since a string is an IEnumerable<char>, we want it to skip the two checks immediately following
if (bindingContext.ModelType != typeof(string)) {
// conversion results in 3 cases, as below
if (bindingContext.ModelType.IsArray) {
// case 1: user asked for an array
// ValueProviderResult.ConvertTo() understands array types, so pass in the array type directly
object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
return modelArray;
}
Type enumerableType = ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>));
if (enumerableType != null) {
// case 2: user asked for a collection rather than an array
// need to call ConvertTo() on the array type, then copy the array to the collection
object modelCollection = CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
Type elementType = enumerableType.GetGenericArguments()[0];
Type arrayType = elementType.MakeArrayType();
object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, arrayType);
Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
if (collectionType.IsInstanceOfType(modelCollection)) {
CollectionHelpers.ReplaceCollection(elementType, modelCollection, modelArray);
}
return modelCollection;
}
}
// case 3: user asked for an individual element
object model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
return model;
}
valueProviderResult.RawValue就是简单类型的参数值,if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) 是判断RawValue是否是指定的参数类型的实例,如果是那就直接返回RawValue。 跳转到最后ConvertProviderResult(…)方法处进行分析,上面的代码都是对数组的操作,最后也还是会调用 ConvertProviderResult(…)方法,来看看ConvertProviderResult(…)方法的代码:
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "We're recording this exception so that we can act on it later.")]
private static object ConvertProviderResult(ModelStateDictionary modelState, string modelStateKey, ValueProviderResult valueProviderResult, Type destinationType) {
try {
object convertedValue = valueProviderResult.ConvertTo(destinationType);
return convertedValue;
}
catch (Exception ex) {
modelState.AddModelError(modelStateKey, ex);
return null;
}
}
调用ValueProviderResult类(ValueProviderResult.cs)的ConvertTo方法, 后又跳转到UnwrapPossibleArrayType(…)方法
public object ConvertTo(Type type) {
return ConvertTo(type, null /* culture */);
}
public virtual object ConvertTo(Type type, CultureInfo culture) {
if (type == null) {
throw new ArgumentNullException("type");
}
CultureInfo cultureToUse = culture ?? Culture;
return UnwrapPossibleArrayType(cultureToUse, RawValue, type);
}
private static object UnwrapPossibleArrayType(CultureInfo culture, object value, Type destinationType) {
if (value == null || destinationType.IsInstanceOfType(value)) {
return value;
}
// array conversion results in four cases, as below
Array valueAsArray = value as Array;
if (destinationType.IsArray) {
Type destinationElementType = destinationType.GetElementType();
if (valueAsArray != null) {
// case 1: both destination + source type are arrays, so convert each element
IList converted = Array.CreateInstance(destinationElementType, valueAsArray.Length);
for (int i = 0; i < valueAsArray.Length; i++) {
converted[i] = ConvertSimpleType(culture, valueAsArray.GetValue(i), destinationElementType);
}
return converted;
}
else {
// case 2: destination type is array but source is single element, so wrap element in array + convert
object element = ConvertSimpleType(culture, value, destinationElementType);
IList converted = Array.CreateInstance(destinationElementType, 1);
converted[0] = element;
return converted;
}
}
else if (valueAsArray != null) {
// case 3: destination type is single element but source is array, so extract first element + convert
if (valueAsArray.Length > 0) {
value = valueAsArray.GetValue(0);
return ConvertSimpleType(culture, value, destinationType);
}
else {
// case 3(a): source is empty array, so can't perform conversion
return null;
}
}
// case 4: both destination + source type are single elements, so convert
return ConvertSimpleType(culture, value, destinationType);
}
在UnwrapPossibleArrayType(…)方法中首先是一些对数组的判断和操作,如果参数的类型不是Array那么就运行最后一行的 ConvertSimpleType(…)方法并返回参数值
private static object ConvertSimpleType(CultureInfo culture, object value, Type destinationType) {
if (value == null || destinationType.IsInstanceOfType(value)) {
return value;
}
// if this is a user-input value but the user didn't type anything, return no value
string valueAsString = value as string;
if (valueAsString != null && valueAsString.Length == 0) {
return null;
}
TypeConverter converter = TypeDescriptor.GetConverter(destinationType);
bool canConvertFrom = converter.CanConvertFrom(value.GetType());
if (!canConvertFrom) {
converter = TypeDescriptor.GetConverter(value.GetType());
}
if (!(canConvertFrom || converter.CanConvertTo(destinationType))) {
string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ValueProviderResult_NoConverterExists,
value.GetType().FullName, destinationType.FullName);
throw new InvalidOperationException(message);
}
try {
object convertedValue = (canConvertFrom) ?
converter.ConvertFrom(null /* context */, culture, value) :
converter.ConvertTo(null /* context */, culture, value, destinationType);
return convertedValue;
}
catch (Exception ex) {
string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ValueProviderResult_ConversionThrew,
value.GetType().FullName, destinationType.FullName);
throw new InvalidOperationException(message, ex);
}
}
TypeConverter converter = TypeDescriptor.GetConverter(destinationType) 来返回一个destinationType类型TypeConverter转换器,并用convert.CanConvertFrom和 CanConvertTo两个方法是否可以从value.GetType()转换为destationType类型,如果可以转换那么就将转换的返回给 Action对应的参数,否则换出一个异常。
2. BindComplexModel
在BindComplexModel中只讨论strongly-typed。先来看看BindComplexModel(…)方法的代码
internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
object model = bindingContext.Model;
Type modelType = bindingContext.ModelType;
// if we're being asked to create an array, create a list instead, then coerce to an array after the list is created
if (model == null && modelType.IsArray) {
Type elementType = modelType.GetElementType();
Type listType = typeof(List<>).MakeGenericType(elementType);
object collection = CreateModel(controllerContext, bindingContext, listType);
ModelBindingContext arrayBindingContext = new ModelBindingContext() {
Model = collection,
ModelName = bindingContext.ModelName,
ModelState = bindingContext.ModelState,
ModelType = listType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType);
if (list == null) {
return null;
}
Array array = Array.CreateInstance(elementType, list.Count);
list.CopyTo(array, 0);
return array;
}
if (model == null) {
model = CreateModel(controllerContext,bindingContext,modelType);
}
// special-case IDictionary<,> and ICollection<>
Type dictionaryType = ExtractGenericInterface(modelType, typeof(IDictionary<,>));
if (dictionaryType != null) {
Type[] genericArguments = dictionaryType.GetGenericArguments();
Type keyType = genericArguments[0];
Type valueType = genericArguments[1];
ModelBindingContext dictionaryBindingContext = new ModelBindingContext() {
Model = model,
ModelName = bindingContext.ModelName,
ModelState = bindingContext.ModelState,
ModelType = modelType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType);
return dictionary;
}
Type enumerableType = ExtractGenericInterface(modelType, typeof(IEnumerable<>));
if (enumerableType != null) {
Type elementType = enumerableType.GetGenericArguments()[0];
Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
if (collectionType.IsInstanceOfType(model)) {
ModelBindingContext collectionBindingContext = new ModelBindingContext() {
Model = model,
ModelName = bindingContext.ModelName,
ModelState = bindingContext.ModelState,
ModelType = modelType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType);
return collection;
}
}
// otherwise, just update the properties on the complex type
BindComplexElementalModel(controllerContext, bindingContext, model);
return model;
}
先从ModelBindingContext中 获取model和modelType,在BindComplexModel方法的中间判断model是否为null,如果不是将调用 CreateModel(…)方法来创建一个strongly-typed对象的实例: if (model == null) { model = CreateModel(controllerContext,bindingContext,modelType); } 。跳转到倒数第二行的BindComplexElementalModel(…)方法处
internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) {
// need to replace the property filter + model object and create an inner binding context
BindAttribute bindAttr = (BindAttribute)TypeDescriptor.GetAttributes(bindingContext.ModelType)[typeof(BindAttribute)];
Predicate<string> newPropertyFilter = (bindAttr != null)
? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName)
: bindingContext.PropertyFilter;
ModelBindingContext newBindingContext = new ModelBindingContext() {
Model = model,
ModelName = bindingContext.ModelName,
ModelState = bindingContext.ModelState,
ModelType = bindingContext.ModelType,
PropertyFilter = newPropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
// validation
if (OnModelUpdating(controllerContext, newBindingContext)) {
BindProperties(controllerContext, newBindingContext);
OnModelUpdated(controllerContext, newBindingContext);
}
}
先获取参数前面的BindAttribute,创建一个新的ModelBindingContext对 象并重新设置了PropertyFilter属性,在下面的if语句中调用了三个方法,OnModelUpdating(…)永远返回true,接着是 BindProperties(…),这个方法为strongly-typed对象的每个属性赋值,来看看它的源码:
private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext);
foreach (PropertyDescriptor property in properties) {
BindProperty(controllerContext, bindingContext, property);
}
}
调用GetModelProperties(…)方法返回一个PropertyDescriptorCollection 集合对象,注意,这个集合不一定是stronly-typed中的全部属性,在GetModelProperties()方法中会根据参 数前面定义的BindAttribute.Incude和BindAttribute.Exclude过滤掉不被使用的属性,将那些确定使用的属性存放到PropertyDescriptorCollection 中。 在foreach中调用BindProperty(…)为每个可用的属性赋值,
protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {
// need to skip properties that aren't part of the request, else we might hit a StackOverflowException
string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, fullPropertyKey)) {
return;
}
// call into the property's model binder
IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
ModelBindingContext innerBindingContext = new ModelBindingContext() {
Model = originalPropertyValue,
ModelName = fullPropertyKey,
ModelState = bindingContext.ModelState,
ModelType = propertyDescriptor.PropertyType,
ValueProvider = bindingContext.ValueProvider
};
object newPropertyValue = propertyBinder.BindModel(controllerContext, innerBindingContext);
// validation
if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {
SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
}
}
在BindProperty(…)内部递归调用IModelBinder.GetBinder(…)方法来为属性赋值。
ModelBinder并不复杂,Action所有的参数值都是循环调用IModelBinder.GetBinder()方法获得,而 complex类型递归调用IModelBinder来为complex类型的可用属性赋值。