今天紧接上篇的进度,继续讨论ModelBinding。
为了衔接顺畅,把上篇最后一段代码再贴一遍,姑且称作代码段1吧。
protected virtual objectGetParameterValue(ControllerContext controllerContext, ParameterDescriptorparameterDescriptor) { // collect all of the necessarybinding properties Type parameterType =parameterDescriptor.ParameterType; IModelBinder binder =GetModelBinder(parameterDescriptor); IValueProvider 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 prefixnot specified ModelMetadata =ModelMetadataProviders.Current.GetMetadataForType(null, parameterType), ModelName = parameterName, ModelState =controllerContext.Controller.ViewData.ModelState, PropertyFilter =propertyFilter, ValueProvider = valueProvider }; object result =binder.BindModel(controllerContext, bindingContext); return result ??parameterDescriptor.DefaultValue; }
代码段 1
这里关键要搞清两点:一是MVC如何得到IModelBinder接口的实例,二是该接口的BindModel方法如何把Request相关信息绑定到相应Model之上。
先看第一点,在代码段1中找到GetModelBinder方法的调用,按F12进入其定义(代码段2)。这个方法只有一条语句,结合“// look on the parameter itself, then look in the global table”这条注释,可以知道MVC先检查传进来的parameterDescriptor.BindingInfo.Binder是否为空,不为空则直接返回该属性,若为空则通过调用Binders.GetBinder方法,在全局表里寻找IModelBinder。
private IModelBinderGetModelBinder(ParameterDescriptor parameterDescriptor) { // look on the parameter itself,then look in the global table returnparameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType); }
代码段 2
进入Binders.GetBinder方法的定义,它有很多重载,直接找到最终调用的版本(代码段3)。
private IModelBinder GetBinder(TypemodelType, IModelBinder fallbackBinder) { // Try to look up a binder for thistype. We use this order of precedence: // 1. Binder returned from provider // 2. Binder registered in theglobal table // 3. Binder attribute defined onthe type // 4. Supplied fallback binder IModelBinder binder =_modelBinderProviders.GetBinder(modelType); if (binder != null) { return binder; } if(_innerDictionary.TryGetValue(modelType, out binder)) { return binder; } // Function is called frequently,so ensure the error delegate is stateless binder =ModelBinders.GetBinderFromAttributes(modelType, (Type errorModel) => { throw new InvalidOperationException( String.Format(CultureInfo.CurrentCulture,MvcResources.ModelBinderDictionary_MultipleAttributes, errorModel.FullName)); }); return binder ?? fallbackBinder; }
代码段 3
微软的开源项目组还是比较重视代码注释的,瞧,这个方法一上来就是一段相当工整的注释。尽管各位的英文都是相当的NB,我还是献丑翻译一下吧:
尝试寻找该类型的binder。我们按照如下顺序进行搜索:
1、provider返回的Binder
2、在全局表里注册的Binder
3、在该类型上定义的Binder特性
4、用户提供的备用Binder
下面逐条讨论这四条的实现原理:
1、provider返回的Binder。
找到如下语句:
IModelBinderbinder = _modelBinderProviders.GetBinder(modelType);
我们看看modelBinderProviders这个私有成员是怎么赋值的,找到其所在类ModelBinderDictionary的构造函数(代码段4)。
public ModelBinderDictionary() :this(ModelBinderProviders.BinderProviders) { } internal ModelBinderDictionary(ModelBinderProviderCollectionmodelBinderProviders) { _modelBinderProviders =modelBinderProviders; }
代码段 4
可见modelBinderProviders是用ModelBinderProviders.BinderProviders赋值的,而ModelBinderProviders.BinderProviders【1】又是什么东东呢?我们看代码段5,这是我们在自己的MVC项目中Global.asax文件中的Application_Start方法,其中第二句(//*处)是我们把自定义的mModelBinderProvider添加到ModelBinderProviders.BinderProviders集合中,这个集合就是上标【1】处的那个什么东东。
protected voidApplication_Start() { AreaRegistration.RegisterAllAreas(); ModelBinderProviders.BinderProviders.Add(newCustomModelBinderProvider());//* RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); }
代码段 5
2、在全局表里注册的Binder。
这个比较简单,就一条代码【_innerDictionary.TryGetValue(modelType, out binder)】,其中的innerDictionary是一个字典对象,意思是在这个字典中寻找该类型的Binder。
3、在该类型上定义的Binder特性。
在代码段3中找到ModelBinders.GetBinderFromAttributes方法,从名称可以猜测出它的作用是从Attributes中获取Binder。下面转到其定义(代码段6),这段代码首先通过GetAttributes()获取类型type的所有特性(Attributes),再调用SingleOfTypeDefaultOrError方法得到用户自定义的CustomModelBinderAttribute,最后通过调用GetBinder方法获得IModelBinder并返回之。
internal static IModelBinder GetBinderFromAttributes(Type type,Action<Type> errorAction) { AttributeList allAttrs = newAttributeList(TypeDescriptorHelper.Get(type).GetAttributes()); CustomModelBinderAttributebinder = allAttrs.SingleOfTypeDefaultOrError<Attribute,CustomModelBinderAttribute, Type>(errorAction, type); return binder == null ? null : binder.GetBinder(); }
代码段 6
SingleOfTypeDefaultOrError方法的定义见代码段7,这是IList<TInput>泛型类的一个扩展方法,作用是从一个IList集合中搜索类型是TMatch的元素,如果集合中只有一个匹配,则返回该元素;若没有匹配,则返回空;若有多于一个的匹配,则调用回调委托errorAction。
/// <summary> /// Returns a single value in list matching type TMatch if there is onlyone, null if there are none of type TMatch or calls the /// errorAction with errorArg1 if there is more than one. /// </summary> public static TMatch SingleOfTypeDefaultOrError<TInput, TMatch,TArg1>(this IList<TInput> list, Action<TArg1> errorAction, TArg1errorArg1) where TMatch : class { Contract.Assert(list != null); Contract.Assert(errorAction != null); TMatch result = null; for (int i = 0; i < list.Count; i++) { TMatch typedValue = list[i] asTMatch; if (typedValue != null) { if (result == null) { result = typedValue; } else { errorAction(errorArg1); return null; } } } return result; }
代码段 7