当我们在Controller中定义一个Action,通常会定义一个或多个参数,每个参数称为一个模型,ASP.NET MVC框架提供了一种机制称为模型绑定,会尝试自动从请求的信息中实例化每一个模型并赋值。这其中又涉及模型的元数据提供和模型的验证。
我们不妨试想一下,如果来定义一种从字符串值到对象值的映射机制,可能要知道以下信息:
1. 对象的类型和名称等对象本身的元数据
2. 对象属性的元数据信息
3. 查询字符串到对象和对象属性的值映射机制
4. 具体的绑定过程
前面的1,2 由ASP.NET MVC框架的元数据提供机制保证,3由其值提供机制保证,4由具体的模型绑定对象执行。只不过ASP.NET MVC在做完模型绑定后,顺带验证了模型的验证。
现在我们来看看具体的代码.参数模型绑定执行肯定在具体的Action方法执行之前,在ControllerActionInvoker的InvokeAction方中,执行Action方法是InvokeActionMethodWithFilters,在其前一行代码是IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor); 这个方法的目的把Action的所有参数值根据参数名封装到一个字典里。现在来看看其实现:
1 protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) 2 { 3 Dictionary<string, object> parametersDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); 4 ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters(); 5 6 foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors) 7 { 8 parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor); 9 } 10 return parametersDict; 11 }
可以看到通过ActionDescriptor获取所有的参数描述信息数组,每个参数通过调用GetParameterValue获取其参数值。这里有一个重要的类ParameterDescriptor,该类是个抽象类其定义如下:
1 public abstract class ParameterDescriptor : ICustomAttributeProvider 2 { 3 4 protected ParameterDescriptor(); 5 public abstract ActionDescriptor ActionDescriptor { get; } 6 7 public virtual ParameterBindingInfo BindingInfo { get; } 8 9 public virtual object DefaultValue { get; } 10 public abstract string ParameterName { get; } 11 public abstract Type ParameterType { get; } 12 public virtual object[] GetCustomAttributes(bool inherit); 13 public virtual object[] GetCustomAttributes(Type attributeType, bool inherit); 14 public virtual bool IsDefined(Type attributeType, bool inherit); 15 }
这个类型除了BindingInfo属性其它属性没什么好说的,BindingInfo的类型是ParameterBindingInfo,描述该参数类型模型绑定过程中将使用的信息,具体那些信息我们来看这个类型的定义:
1 public abstract class ParameterBindingInfo 2 { 3 protected ParameterBindingInfo(); 4 public virtual IModelBinder Binder { get; } 5 public virtual ICollection<string> Exclude { get; } 6 public virtual ICollection<string> Include { get; } 7 public virtual string Prefix { get; } 8 }
Binder属性是指参数特定的绑定器,通过ModelBinderAttribute来指定, Exclude和Include表示不参与或参与参数属性模型绑定列表,Prefix 表示绑定过程中使用的前缀,这三个属性是通过BindAttribute属性来设置的。
现在回过头来看看GetParameterValue 方法的实现:
1 protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) 2 { 3 // collect all of the necessary binding properties 4 Type parameterType = parameterDescriptor.ParameterType; 5 IModelBinder binder = GetModelBinder(parameterDescriptor); 6 IValueProvider valueProvider = controllerContext.Controller.ValueProvider; 7 string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName; 8 Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor); 9 10 // finally, call into the binder 11 ModelBindingContext bindingContext = new ModelBindingContext() 12 { 13 FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified 14 ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType), 15 ModelName = parameterName, 16 ModelState = controllerContext.Controller.ViewData.ModelState, 17 PropertyFilter = propertyFilter, 18 ValueProvider = valueProvider 19 }; 20 21 object result = binder.BindModel(controllerContext, bindingContext); 22 return result ?? parameterDescriptor.DefaultValue; 23 }
a. 前面的几行代码准备绑定上下文使用的信息,简单的这里不说了,具体的ModelBinder通过GetModelBinder方法获取,它的实现是这样的:
return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);
我们可以知道,通过ModelBinderAttribute指定的ModelBinder具有最高的优先级,Binders是ModelBinders.Binders静态属性,如果参数未指定ModelBinder,则通过ModelBinders.Binders.ModelBinders.Binders.GetBinder方法查找,具体查找这里不分析了,这里给出一个结果:
1. 在参数上应用了ModelBinderAttribute
2. 在ModelBinderProvider集合中查找
3. 在全局注册的模型绑定集合表中查找
4. 在参数类型上通过CustomModelBinderAttribute指定的模型绑定类型
5. 返回默认的DefaultModelBinder
b. 实例化绑定上文下,我们看到如果我们通过BinderAttribute的Prefix指定了前缀,系统默认的FallbackToEmptyPrefix将不再使用, 另外参数模型的元数据通过调用系统的元数据提供机制ModelMetadataProviders.Current.GetMetadataForType.
c. 执行模型绑定,如果返回null再返回参数指定的默认值
这段代码信息量比较大,包含了元数据提供机制,值提供机制,模型绑定与验证。后面的章节将分别描述。