通过实例模拟ASP.NET MVC的Model绑定机制:简单类型+复杂类型2012-09-19 cnblogs Artech总的来说,针对目标Action方法参数的Model绑定完全由组件ModelBinder来实现,在默认情况下使用的ModelBinder类型为DefaultModelBinder,接下来我们将按照逐层深入的方式介绍实现在DefaultModelBinder的默认Model绑定机制。[一、简单类型对于旨在绑定目标Action方法参数值的Model来说,最简单的莫过于简单参数类型的情况。通过《初识Model元数据》的介绍我们知道,复杂类型和简单类型之间的区别仅仅在于是否支持针对字符串类型的转换。由于参数值的数据源在请求中以字符串的形式存在,对于支持字符串转换的简单类型来说,可以直接通过类型转换得到参数值。我们通过一个简单的实例来模拟实现在DefaultModelBinder中针对简单类型的Model绑定。如下所示的是我们自定义的DefaultModelBinder,其属性ValueProvider用于从请求中提供相应的数据值,该属性在构造函数中被初始化。
 1: public class DefaultModelBinder
 2: {
 3: public IValueProvider ValueProvider { get; private set; }
 4: public DefaultModelBinder(IValueProvider valueProvider)
 5: {
 6: this.ValueProvider = valueProvider;
 7: }
 8: 
 9:public IEnumerable<object> GetParameterValues(ActionDescriptor actionDescriptor)
10: {
11: foreach (ParameterDescriptor parameterDescriptor in actionDescriptor.GetParameters())
12: {
13: string prefix = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
14: yield return GetParameterValue(parameterDescriptor, prefix);
15: }
16: }
17: 
18: public object GetParameterValue(ParameterDescriptor parameterDescriptor, string prefix)
19: {
20: object parameterValue = BindModel(parameterDescriptor.ParameterType, prefix);
21: if (null == parameterValue && string.IsNullOrEmpty(parameterDescriptor.BindingInfo.Prefix))
22: {
23: parameterValue = BindModel( parameterDescriptor.ParameterType, "");
24: }
25: return parameterValue ?? parameterDescriptor.DefaultValue;
26:}
27: 
28: public object BindModel(Type parameterType, string prefix)
29: {
30: if (!this.ValueProvider.ContainsPrefix(prefix))
31: {
32: return null;
33: }
34: return this.ValueProvider.GetValue(prefix).ConvertTo(parameterType);
35: }
36: }
方法GetParameterValues根据指定的用于描述Action方法的ActionDescriptor获取最终执行该方法的所有参数值。在该方法中,我们通过调用ActionDescriptor的GetParameters方法得到用于描述其参数的所有ParameterDescriptor对象,并将每一个ParameterDescriptor作为参数调用GetParameterValue方法得到具体某个参数的值。GetParameterValue除了接受一个类型为ParameterDescriptor的参数外,还接受一个用于表示前缀的字符串参数。如果通过ParameterDescriptor的BindingInfo属性表示的ParameterBindingInfo对象具有前缀,则采用该前缀;否则采用参数名称作为前缀。对于GetParameterValue方法来说,它又通过调用另一个将参数类型作为参数的BindModel方法来提供具体的参数值,BindModel方法同样接受一个表示前缀的字符串作为其第二个参数。GetParameterValue最初将通过ParameterDescriptor获取到的参数值和前缀作为参数调用BindModel方法,如果返回值为Null并且参数并没有显示执行前缀,会传入一个空字符串作为前缀再一次调用BindModel方法,这实际上模拟了之前提到过的去除前缀的后备Model绑定机制(针对于ModelBindingContext的FallbackToEmptyPrefix属性)。如果最终得到的对象不为Null,则将其作为参数值返回;否则返回参数的默认值。BindModel方法的逻辑非常简单。先将传入的前缀作为参数调用ValueProvider的ContainsPrefix方法判断当前的ValueProvider保持的数据是否具有该前缀。如果返回之为False,直接返回Null,否则以此前缀作为Key调用GetValue方法得到一个ValueProviderResult调用,并最终调用ConvertTo方法转换为参数类型并返回。为了验证我们自定义的DefaultModelBinder能够真正地用于针对简单参数类型的Model绑定没我们将它应用到一个具体的ASP.NET MVC应用中。在通过Visual Studio的ASP.NET MVC项目模板创建的空Web应用中,我们创建了如下一个默认的HomeController。HomeController具有一个ModelBinder属性,其类型正是我们自定义的DefaultModelBinder,该属性通过方法GetValueProvider提供。
 1: public class HomeController : Controller
 2: {
 3: public DefaultModelBinder ModelBinder { get; private set; }
 4: public HomeController()
 5: {
 6: this.ModelBinder = new DefaultModelBinder(GetValueProvider());
 7: }
 8: private void InvokeAction(string actionName)
 9: {
10: ControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(typeof(HomeController));
11: ReflectedActionDescriptor actionDescriptor = (ReflectedActionDescriptor)controllerDescriptor
12: .FindAction(ControllerContext, actionName);
13: actionDescriptor.MethodInfo.Invoke(this,this.ModelBinder.GetParameterValues(actionDescriptor).ToArray());
14: }
15: public void Index()
16: {
17: InvokeAction("Action");
18: }
19: 
20: private IValueProvider GetValueProvider()
21: {
22: NameValueCollection requestData = new NameValueCollection();
23: requestData.Add("foo", "abc");
24: requestData.Add("bar", "123");
25: requestData.Add("baz", "123.45");
26: return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);
27: }
28: public void Action(string foo, [Bind(Prefix="baz")]double bar)
29: {
30: Response.Write(string.Format("{0}: {1}<br/>", "foo", foo));
31: Response.Write(string.Format("{0}: {1}<br/>", "bar", bar));
32: }
33: }