上一篇《ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(上篇)》文章介绍了ASP.NET MVC模型绑定的相关组件和概念,本章将介绍Controller在执行时是如何通过这些组件完成模型绑定功能的,本章的主要内容有:
● 模型绑定过程
○ 获取ModelBinder
○ 获取ValueProvider
○ 创建ModelMetadata
○ 模型绑定
● DefaultModelBinder的模型绑定过程
○ 简单模型绑定
○ 复杂模型绑定
● 小结
模型的绑定过程
之前的文章介绍过MVC中Controller的创建与执行(可参考:《ASP.NET没有魔法——ASP.NET MVC Controller的实例化与执行》),其执行过程包含临时数据加载、Action方法名称获取、Action方法执行及临时数据保存。
而模型绑定过程实际上就包含在Action方法的执行(ActionInvoker.InvokeAction方法)中,之前的文章《ASP.NET没有魔法——ASP.NET MVC 过滤器(Filter)》也通过从过滤器的角度介绍了Action的执行,其中一点就是当授权过滤器执行完成并通过授权后将开始执行其它过滤器与Action方法,如下图:
而Action方法的执行核心代码如下:
代码中的GetParameterValues方法的作用就是根据Controller的上下文以及Action的描述信息来获取Action参数的过程,也就是模型绑定的过程,其代码如下:
上面代码就是获取Action的参数数组,然后遍历这个数组,通过GetParameterValue方法完成单个参数的绑定工作,最后以参数名称为Key,绑定后的值为对象存储在字典中返回。下面是GetParameterValue方法的具体代码:
从代码看来整个绑定过程有如下几个步骤:
1. 根据参数获取ModelBinder。
2. 从Controller中获取值提供器。
3. 创建模型绑定上下文,该上下文中包含Metadata、模型名称、值提供器等内容。
4. 通过获取的ModelBinder完成模型绑定。
接下来就对这些核心步骤进行分析。
获取ModelBinder
代码中调用GetModelBinder方法,根据参数信息获取一个ModelBinder,下图是实现代码:
从代码中可以看出,获取ModelBinder的方式有两种,首先从通过参数描述信息(ParameterDescriptor)来获取通过特性标记的方式应用到该参数的ModelBinder,其核心代码如下(ParameterDescriptor默认使用的类型是ReflectedParameterDescriptor,通过反射的方式获取特性):
注:更多关于通过特性应用自定义ModelBinder的内容可参考:https://www.codeproject.com/articles/605595/asp-net-mvc-custom-model-binder
当参数上没有通过特性指定ModelBinder时,将通过Binders根据参数类型来获取ModelBinder,这里的Binders实际上就是ModelBinderDictionary,前面介绍过这个字典中以ModelBinder的类型名称为Key保存了所有的ModelBinder,ModelBinderDictionary的GetBinder方法如下:
首先通过ModelBinderProviders查找(注:默认情况下ASP.NET MVC没有使用ModelBinderProvider:http://www.dotnetcurry.com/aspnet-mvc/1261/custom-model-binder-aspnet-mvc),然后再通过类型名称在字典中匹配对应的ModelBinder,最后从模型类型上找是否通过特性指定了Modelbinder。
如果以上都找不到那么使用默认的ModelBinder,ModelBinderDictionary的结构如下图:
获取ValueProvider
ValueProvider用于提供数据,此处直接使用Controller中的ValueProvider:
而Controller中的ValueProvider是遍历所有ValueProviderFactory根据Controller上下文创建的一个提供器集合:
一般来说所有的提供器都会被创建,如下图的FormValue提供器,只要Controller上下文不为空就会有:
但是Json提供器比较特殊,需要请求内容为Json格式并能够正确反序列化才会创建,这也就解释了文章开始时只有发送Json请求时才会有Json值提供器存在的问题,下面是Json值提供器创建的代码,必须保证反序列化的对象不为空才会将该提供器保存到字典中:
而反序列化GetDseserializedObject方法,则是需要请求类型为Json,才会进行反序列化工作:
创建ModelMetadata
ModelMetadata是用来描述模型的元数据,在进行模型绑定时元数据被包含在模型绑定上下文(ModelBindingContext)对象中,这个上下文对象除了模型元数据外,还包含属性过滤器以及模型名称等信息。
而代码显示元数据获取实际上是通过一个元数据提供器的组件完成的其代码如下:
根据代码分析,实际上使用了一个名为CachedDataAnnotationsModelMetadataProvider的元数据提供器,其获取Metadata的代码如下:
先获取特性列表,然后通过该特性列表创建Metadata,获取特性很好理解,而创建Metadata的核心代码如下(部分),它实例化一个元数据对象后,通过在特性列表中查找特定的特性然后赋值的方式实现了元数据的创建:
注:实际上ASP.NET MVC中将元数据特性分为以下几种,除了数据类型、必填等属性外,还包含了很多用于展示的特性,这些展示特性将在渲染View的时候使用,更多内容也将在后续介绍:
另外ASP.NET MVC中默认使用基于缓存机制的元数据组件以提高性能,相关类型如下图所示,本文不再详解:
模型绑定
当完成以上步骤后,将通过最开始获取的ModelBinder通过ControllerContext(包含请求数据)以及BindingContext(包含值提供器、模型信息等数据)完成模型绑定。之前也介绍过ModelBinder是根据参数的类型来获取的,比如对于文件数据将会使用HttpPostedFileBaseModelBinder类型来完成,如下图代码就是文件模型绑定的代码:
代码中直接根据模型名称(即参数名称)从请求的文件中获取文件数据,简单直接。
但在模型绑定中很少有如文件这种特殊类型的绑定,更多的是一些基础类型以及自定义类型的绑定,而微软并没有针对每一个基础类型都创建了对应的绑定器,所有的一切都是通过DefaultModelBinder完成的,所以DefaultModelBinder的绑定过程相对于特定类型的模型绑定过程来说相对要复杂。
DefaultModelBinder的模型绑定过程
上面提到了模型绑定的大部分情况是使用DefaultModelBinder完成的,下面就对该绑定器如何完成模型绑定进行介绍,先上代码:
上面代码做了以下几件事:
1. 确保有足够的栈空间来执行.Net中平均大小的函数。
2. 如果模型名称不为空、数据源中不包含以该模型名称为前缀的值、并且参数绑定信息没有前缀时,重新初始化绑定上下文,相比原有上下文去除FallbackToEmptyPrefix以及ModelName属性并将该模型视为复杂模型:
注:上面这种情况可参考《ASP.NET没有魔法——ASP.NET MVC 模型绑定》文章中的例子,该例子中存在参数名obj,但是QueryString中没有使用obj作为前缀,action参数也没有使用bind特性指定前缀,这样的类型将被视为复杂类型,如下图:
3. 获取是否验证标记,并根据模型名称从ValueProvider中获取数据,获取的数据为ValueProviderResult类型,如果该结果存在那么进行简单模型绑定。(注:ValueProviderResult负责简单类型转换,后续介绍,另外这里的验证涉及到.Net中System.Web的基础实现,所以不再进行深入介绍)
4. 如果模型属于复杂模型,则进行复杂模型绑定。
默认模型绑定器中看到了简单类型与复杂类型概念的使用,从代码中看出模型绑定的核心内容也存在于简单、复杂模型的绑定过程中。
简单模型绑定
之前介绍了简单类型,实际上就是可以从字符串直接转换的类型,除此之外还可以通过自定义类型转换器的方式将复杂类型转换为简单类型。
那么对于简单模型绑定来说,实际上核心就是获取到对应的值后,使用类型转换器将获取到的值转换为目标类型。下面是简单模型绑定的实现代码:
根据代码可以看出简单模型绑定核心如下:
1. 如果模型类型与从值提供器中获取的类型一致,那么不需要转换就可以直接返回。
2. 类型转换的核心方法是DefaultModelBinder.ConvertProviderResult,实际是获取类型转换器完成了转换功能,最终转换代码如下(ValueProviderResult的ConvertSimpleType方法,更多细节可参考反编译代码):
3. 简单模型的转换考虑到了数组类型以及集合类型的转换。
复杂模型绑定
一般来说基本类型和一些常用的类型均为简单类型,而复杂模型实际上是由一系列简单类型组成的,所以复杂模型的绑定是将其拆分成一个个简单类型完成绑定后再组装的过程,其主要代码如下(由于完整代码较多,本文只介绍关键点,更多信息可参考DefaultModelBinder的反编译源码):
1 internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 2 { 3 object model = bindingContext.Model; 4 Type modelType = bindingContext.ModelType; 5 if (model == null && modelType.IsArray) 6 { 7 Type elementType = modelType.GetElementType(); 8 Type modelType2 = typeof(List<>).MakeGenericType(new Type[] 9 { 10 elementType 11 }); 12 object collection = this.CreateModel(controllerContext, bindingContext, modelType2); 13 ModelBindingContext bindingContext2 = new ModelBindingContext 14 { 15 ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => collection, modelType2), 16 ModelName = bindingContext.ModelName, 17 ModelState = bindingContext.ModelState, 18 PropertyFilter = bindingContext.PropertyFilter, 19 ValueProvider = bindingContext.ValueProvider 20 }; 21 IList list = (IList)this.UpdateCollection(controllerContext, bindingContext2, elementType); 22 if (list == null) 23 { 24 return null; 25 } 26 Array array = Array.CreateInstance(elementType, list.Count); 27 list.CopyTo(array, 0); 28 return array; 29 } 30 else 31 { 32 if (model == null) 33 { 34 model = this.CreateModel(controllerContext, bindingContext, modelType); 35 } 36 Type type = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<, >)); 37 if (type != null) 38 { 39 Type[] genericArguments = type.GetGenericArguments(); 40 Type keyType = genericArguments[0]; 41 Type valueType = genericArguments[1]; 42 ModelBindingContext modelBindingContext = new ModelBindingContext(); 43 modelBindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType); 44 modelBindingContext.ModelName = bindingContext.ModelName; 45 modelBindingContext.ModelState = bindingContext.ModelState; 46 modelBindingContext.PropertyFilter = bindingContext.PropertyFilter; 47 modelBindingContext.ValueProvider = bindingContext.ValueProvider; 48 ModelBindingContext bindingContext3 = modelBindingContext; 49 return this.UpdateDictionary(controllerContext, bindingContext3, keyType, valueType); 50 } 51 Type type2 = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>)); 52 if (type2 != null) 53 { 54 Type type3 = type2.GetGenericArguments()[0]; 55 Type type4 = typeof(ICollection<>).MakeGenericType(new Type[] 56 { 57 type3 58 }); 59 if (type4.IsInstanceOfType(model)) 60 { 61 ModelBindingContext modelBindingContext2 = new ModelBindingContext(); 62 modelBindingContext2.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType); 63 modelBindingContext2.ModelName = bindingContext.ModelName; 64 modelBindingContext2.ModelState = bindingContext.ModelState; 65 modelBindingContext2.PropertyFilter = bindingContext.PropertyFilter; 66 modelBindingContext2.ValueProvider = bindingContext.ValueProvider; 67 ModelBindingContext bindingContext4 = modelBindingContext2; 68 return this.UpdateCollection(controllerContext, bindingContext4, type3); 69 } 70 } 71 this.BindComplexElementalModel(controllerContext, bindingContext, model); 72 return model; 73 } 74 }
1. 从绑定上下文中获取模型对象(注:因为复杂模型绑定中存在绑定方法的递归调用,所以可能会多次调用BindComplexModel方法,而绑定上下文的模型对象就是用于保存当前递归的所属对象)。
2. 如果model不存在则创建,首次绑定复杂对象时model均为null:
3. 开始绑定该模型中的基本模型,从代码中可以看到实际上就是对模型中的属性进行绑定:
4. 属性绑定是获取模型的所有属性,然后遍历并完成所有属性的绑定,属性绑定代码如下:
a. 获取属性对应类型的ModelBinder:
b. 调用ModelBinder的BindModel方法(如果该属性为简单模型,那么执行简单模型绑定代码,否则递归调用复杂模型绑定代码)
c. 将获取的结果赋值给Model对象,然后获取属性的验证器并对该属性进行验证,错误信息保存到ModelState列表中:
注:ModelValidatorProvider是用来获取模型验证器的工具,在ASP.NET MVC中有三种内置的验证提供器如下图所示:
其中DataAnnotationsModelValidatorProvider用来获取通过特性标记的模型验证器,也就是《ASP.NET没有魔法——ASP.NET MVC 模型验证》文章中介绍的方法,而DataErrorInfoModelValidatorProvider是一种通过实现IDataErrorInfo接口来实现数据功能的提供器,更多信息可参考:https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/models-data/validating-with-the-idataerrorinfo-interface-cs
https://www.codeproject.com/articles/39012/form-validation-with-asp-net-mvc-using-the-idataerrorinfo-interface.aspx
最后ClientDataTypeModelValidatorProvider用于客户端验证。
注:从代码可以看出ASP.NET MVC中基于特性标记的数据验证功能仅对复杂模型进行验证,在简单特性上使用验证特性是无效的。
test123长度已经超出6,但是模型状态仍然是有效的。
小结
本文主要是基于源码介绍了ASP.NET MVC中模型绑定的过程,简单来说模型绑定核心过程如下:
● 根据参数类型获取ModelBinder。
● 获取ValueProvider,如果是Json请求那么创建Json的ValueProvider。
● 根据特性标记获取ModelMetadata。
● 根据模型类型(简单/复杂)进行数据绑定,简单类型主要是字符串转换为其它简单类型的过程(包括数组/集合的转换),而复杂类型是将类型本身分解为简单类型后一一绑定并验证的过程。
参考:
https://www.codeproject.com/articles/605595/asp-net-mvc-custom-model-binder
http://www.dotnetcurry.com/aspnet-mvc/1261/custom-model-binder-aspnet-mvc
https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/models-data/validating-with-the-idataerrorinfo-interface-cs
https://www.codeproject.com/articles/39012/form-validation-with-asp-net-mvc-using-the-idataerrorinfo-interface.aspx
http://dirk.schuermans.me/?p=749