当我们在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的所有参数值根据参数名封装到一个字典里。现在来看看其实现:
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获取所有的参数描述信息数组,每个参数通过调用GetParameterValue获取其参数值。这里有一个重要的类ParameterDescriptor,该类是个抽象类其定义如下:
public abstract class ParameterDescriptor : ICustomAttributeProvider
{ protected ParameterDescriptor();
public abstract ActionDescriptor ActionDescriptor { get; } public virtual ParameterBindingInfo BindingInfo { get; } public virtual object DefaultValue { get; }
public abstract string ParameterName { get; }
public abstract Type ParameterType { get; }
public virtual object[] GetCustomAttributes(bool inherit);
public virtual object[] GetCustomAttributes(Type attributeType, bool inherit);
public virtual bool IsDefined(Type attributeType, bool inherit);
}
这个类型除了BindingInfo属性其它属性没什么好说的,BindingInfo的类型是ParameterBindingInfo,描述该参数类型模型绑定过程中将使用的信息,具体那些信息我们来看这个类型的定义:
public abstract class ParameterBindingInfo
{
protected ParameterBindingInfo();
public virtual IModelBinder Binder { get; }
public virtual ICollection<string> Exclude { get; }
public virtual ICollection<string> Include { get; }
public virtual string Prefix { get; }
}
Binder属性是指参数特定的绑定器,通过ModelBinderAttribute来指定, Exclude和Include表示不参与或参与参数属性模型绑定列表,Prefix 表示绑定过程中使用的前缀,这三个属性是通过BindAttribute属性来设置的。
现在回过头来看看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;
}
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再返回参数指定的默认值
这段代码信息量比较大,包含了元数据提供机制,值提供机制,模型绑定与验证。后面的章节将分别描述。