深入ASP.NET MVC之六:Model Binding的实现
在Filter和Action的执行 中说到,ControllerActionInvoker对象在InvokeAction方法中调用了GetParameters方法实现了model binding,先来看下这个方法:
protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { Dictionary<string, object> parametersDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters(); foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors) { parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor); } return parametersDict; }
首先通过actionDescriptor获得action参数的信息,这里的actionDescriptor实际上是一个ReflectedActionDescriptor,获得参数的方法自然是通过反射,不深入分析了。紧接着就对每个参赛调用了GetParameterValue方法:
protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) { // collect all of the necessary binding 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 prefix not 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; }
这个方法中完成了几件事,首先是获得Model Binder,其次是获得Value Provider,再获得Property Filter,最终把这些信息组成ModelBindingContext,交给binder的BindModel方法实现绑定。先看如何获得Model Binder和Value Provider,这里将是扩展model binding的着手点。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); }
如果通过action的参数的Atrribute说明了采用什么binder的话就优先使用这个binder,否则从全局binder中查找。这里Binders是一个ModelBinderDictionary对象,其初始化是在ModelBinders类中的CreateDefaultBinderDictionary方法:
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() }, { typeof(byte[]), new ByteArrayModelBinder() }, { typeof(Binary), new LinqBinaryModelBinder() } }; return binders; }
在这里准备了几个默认的ModelBinder.准备工作完成之后,看真正的GetBinder方法,这个方法经过几个重载的方法之后,最终调用的是如下的方法:
return GetBinder(modelType, (fallbackToDefault) ? DefaultBinder : null);
这里fallbackToDefault是true,DefaultBinder就是一个DefaultModelBinder对象,这个方法的实现如下:
private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder) { // Try to look up a binder for this type. We use this order of precedence: // 1. Binder returned from provider // 2. Binder registered in the global table // 3. Binder attribute defined on the type // 4. Supplied fallback binder IModelBinder binder = _modelBinderProviders.GetBinder(modelType); if (binder != null) { return binder; } if (_innerDictionary.TryGetValue(modelType, out binder)) { return binder; } binder = ModelBinders.GetBinderFromAttributes(modelType, () => String.Format(CultureInfo.CurrentCulture, MvcResources.ModelBinderDictionary_MultipleAttributes, modelType.FullName)); return binder ?? fallbackBinder; }
加上注释的帮助,很容易理解获取一个类型对应的model binder的过程是如何的。首先是通过IModelBinderProvider来查找,这里的modelBinderProviders在默认情况下就是ModelBinderProviders.BinderProviders属性,这是ModelBinderProviderCollection类型的对象。我们要使用自定义的model binder,一个方法就是实现一个IModelBinderProivder,并且通过ModelBinderProviders.BinderProviders.Add方法注册到全局的provider表中。第二个途径是通过现有的binder表,这里的_innerDictionary是一个Dictionary<Type, IModelBinder>类型的对象,也就是ModelBinderDictionary实际存储数据的地方,ModelBinders.Binders.Add方法就会直接往这个dictionary中添加数据。第三个途径是通过待绑定类型的Attribute来加载binder。最后,如果上面的途径都没有找到binder,那么就用默认的DefaultModelBinder,大多数时候这个DefaultModelBinder已经足够强大。暂时先跳过这个DefaultModelBinder的实现,再回到GetParameterValue中,当得到合适的model binder之后还需要获得value provider:
IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
这里的ValueProvider是定义在ControllerBase类型中的:
public IValueProvider ValueProvider { get { if (_valueProvider == null) { _valueProvider = ValueProviderFactories.Factories.GetValueProvider(ControllerContext); } return _valueProvider; } set { _valueProvider = value; } }
下面来看下 ValueProviderFactories的实现:
public static class ValueProviderFactories { private static readonly ValueProviderFactoryCollection _factories = new ValueProviderFactoryCollection() { new ChildActionValueProviderFactory(), new FormValueProviderFactory(), new JsonValueProviderFactory(), new RouteDataValueProviderFactory(), new QueryStringValueProviderFactory(), new HttpFileCollectionValueProviderFactory(), }; public static ValueProviderFactoryCollection Factories { get { return _factories; } } }
这个类初始化了一系列默认的ValueProviderFactory。再看下GetValueProvider(ControllerContext) 这个方法的实现:
public IValueProvider GetValueProvider(ControllerContext controllerContext) { var valueProviders = from factory in _serviceResolver.Current let valueProvider = factory.GetValueProvider(controllerContext) where valueProvider != null select valueProvider; return new ValueProviderCollection(valueProviders.ToList()); }
这里的做法是返回所有的能够找到的value provider,再将其组合成一个ValueProviderCollection.再看下ValueProviderCollection的关键方法的实现:
public virtual ValueProviderResult GetValue(string key) { return GetValue(key, skipValidation: false); } public virtual ValueProviderResult GetValue(string key, bool skipValidation) { return (from provider in this let result = GetValueFromProvider(provider, key, skipValidation) where result != null select result).FirstOrDefault(); } internal static ValueProviderResult GetValueFromProvider(IValueProvider provider, string key, bool skipValidation) { // Since IUnvalidatedValueProvider is a superset of IValueProvider, it's always OK to use the // IUnvalidatedValueProvider-supplied members if they're present. Otherwise just call the // normal IValueProvider members. IUnvalidatedValueProvider unvalidatedProvider = provider as IUnvalidatedValueProvider; return (unvalidatedProvider != null) ? unvalidatedProvider.GetValue(key, skipValidation) : provider.GetValue(key); }
这个方法返回的是第一个能够找到value的ValueProvider返回的值。附上函数调用图: