前面文章介绍了 ASP.NET MVC 中的模型绑定和验证功能, 本着 ASP.NET MVC 没有魔法的精神, 本章内容将从代码的角度对 ASP.NET MVC 如何完成模型的绑定和验证进行分析, 已了解其原理
本文的主要内容有:
- ModelBinder
- ValuePrivoder
- ModelMetadata
简单模型与复杂模型
小结
ModelBinder
ModelBinder 是 ASP. NET MVC 用于模型绑定的核心组件, 所有的 ModelBinder 都实现了 IModelBinder 接口, 如下图:
该接口只有一个方法, 那就是根据控制器以及绑定上下文完成模型绑定
在 ASP.NET MVC 中有不同的 ModelBinder, 它们分别用于绑定不同类型的数据, 如普通的. Net 对象 HTTP 上传的文件等
默认有以下 5 种 ModelBinder:
DefaultModelBinder: 默认的模型绑定器, 一般情况下从浏览器提交的请求都将使用默认处理器来绑定模型
HttpPostedFileBaseModelBinder:HTTP 文件模型绑定
ByteArrayModelBinder: 绑定二进制数据
LinqBinaryModelBinder: 将请求绑定到 System.Data.Linq.Binary 对象参考: http://stephenwalther.com/archive/2009/02/25/asp-net-mvc-tip-49-use-the-linqbinarymodelbinder-in-your
CancellationTokenModelBinder: 提供了一个用于传播模型绑定操作取消的机制
所有的 ModelBinder 都被一个名为 ModelBinderDictionary 的字典进行管理, 而这个字典就存在于 Controller 类型的定义中, 如下图所示, 它是一个被保护的内部属性, 用于 Controller 执行时完成模型绑定:
ModelBinderDictionary 的定义:
从 ModelBinderDictionary 的定义中可以看到它实现了以 Type 为 KeyIModelBinder 类型为值的字典接口以及集合接口, 可以动态的根据 ModelBinder 的类型增减 ModelBinder, 除此之外还有一个 DefaultBinder, 在一般情况下其运行的实例如下:
Controller 中的 ModelBinder 字典包含了上面介绍的 5 个 ModelBinder 更多关于自定义 ModelBinder 的内容可参考: https://www.cnblogs.com/Cwj-XFH/p/5977508.html
ValuePrivoder
在前面的文章中介绍了 ASP.NET MVC 的模型绑定可以从 Form DataQuery String 以及 Route Data 等数据源中获取数据, 其原因是针对每一个数据源都有一个专门的数据提供器来获取数据源的数据, 在 ASP.NET MVC 中存在一个定义值提供器的接口 IValueProvider:
核心方法 GetValue 通过一个 Key 来获取值, ContainsPrefix 则判断提供器所指的数据源中是否包含以指定字符串为前缀的 Key
直接实现该接口的类型有 3 个:
NameValueCollectionValueProvider: 通过名称和值共同存储数据的集合, 可以通过名称查找到一个或多个值
DictionaryValueProvider<TValue>: 通过键值对存储数据的泛型字典集合, 字典的 Key 是唯一的, 换句话说通过 Key 最多只能找到一个值
ValueProviderCollection: 一个特殊的值提供器, 它包含了所有相关的值提供器, 在模型绑定中就是这个列表通过遍历的方式, 调用相关值提供器的获取值方法来完成数据获取的
之前说过针对不同的数据源都有一个特定值提供器, 那么这些提供器是如何实现的呢? 它们是根据特性分别继承 NameValueCollectionValueProvider 及 DictionaryValueProvider<TValue > 类型来实现的, 其具体分类如下, 一共有 7 种不同数据源的值提供器:
NameValueCollectionValueProvider:
JQueryFormValueProvider: 用于获取被 Jquery 格式化的 Form 值
FormValueProvider: 用于获取 Form 表单的值
QueryStringValueProvider: 用于获取查询字符串的值
DictionaryValueProvider<TValue>
ChildActionValueProvider: 用于获取子 Action 方法的值
JsonValueProvider: 用于获取请求中以 Json 格式传输的值(注: 没有该类型的值提供器, Json 的值提供器直接由 JsonValueProviderFactory 创建一个 DictionaryValueProvider<object > 类型的字典值提供器)
HttpFileCollectionValueProvider: 用于从 Http 请求中的文件集合中获取文件数据
RouteDataValueProvider: 用于从 Route Data 中获取值
所有的值提供器都是由对应的工厂创建的, 在默认情况下 ASP.NET MVC 中存在以下 7 种值提供器工厂, 刚好对应上面的 7 种值提供器, 其实现接口定义如下:
每一个工厂的 GetValueProvider 方法获取对应的值提供器
所有的提供器工厂在 MVC 中被一个名为 ValueProviderFactories 的类型维护, 该类型以硬编码的方式维护了一个静态只读的提供器工厂列表:
运行时结果, 一共有 7 个工厂:
下图是 ASP.NET MVC 在未特殊配置的情况下 Controller 的运行状态:
下图是发送 Json 格式 Post 请求的状态, ValueProvider 中多了一个用来获取 Json 数据的字典值提供器:
发起请求的内容:
请求中的值提供器与之前的相比多了一个用于提供 Json 数据的 DictionaryValueProvider<object > 类型:
ModelMetadata
Metadata 译为元数据, 是一种描述数据的数据, 而这里加上了 Model, 那么就是说描述 Model 数据的数据, 下图为 ASP.NET MVC 中的一个 Model 定义:
从图中代码用语言可以这样描述:
该对象有 3 个属性
其中 UserName 是 String 类型的, 必填并且格式为 Email 格式, 展示名称为用户名
Password 以及 ConfirmPassword 都是 String 类型, 且类型都为密码, 它们除了展示名称不同外, ConfirmPassword 还需要和 Password 相比较, 如果不同则给出相应的错误提示
而在 MVC 里面是通过 ModelMatedata 来对 Model 进行描述的, 先看一下 ASP.NET MVC 中的 ModelMetadata 类型:
从中可以看到一些是否只读是否必填模型类型属性 (同样是 ModelMetadata 类型) 展示名称是否是复杂类型等描述信息换句话就是 ASP.NET MVC 通过 ModelMetadata 可以对模型的属性是否只读是否必填类型等相应信息进行描述, 甚至还包含了模型验证器来完成合法性验证
这里需要注意的是模型类型本身通过一个 ModelMetadata 来描述, 而类型的属性同样被 ModelMetadata 描述, 就是说 ModelMetadata 描述类型的结构是与对应类结构有相同的层次
注: ModelMetadata 涉及到 View 的渲染, 关于 View 的内容会在后续文章中介绍
简单模型与复杂模型
在 ModelMetadata 类型中有一个名为 IsComplexType 的属性, 用于表示该类型是否为复杂类型, 那么什么是复杂类型? 相对应的什么是简单类型?
上图是 IsComplexType 的实现代码, 其核心有两个点:
1. 通过当前模型类型来获取一个转换器(注: TypeDescriptor 是一个用来获取类型相关信息的类型, 如特性属性事件等, 当然也包括类型转换器, 下图是 TypeDescriptor 部分定义)
2. 获取到类型转换器后, 通过转换器判断该类型是否能够从字符串转换, 如果能那么它就是简单类型否则为复杂类型(下图为 TypeConverter 的部分定义)
知道了简单类型与复杂类型的区别, 那么它们在 MVC 中有什么意义呢?
首先对于 ASP.NET MVC 来说, 它接收到的 Http 请求无论 HeaderBody 等它实质上都是字符串, 那么根据 Http 协议从中取出来的数据也是字符串, 但是对于 MVC 的 Action 方法来说, 它接受的参数可能是字符串的, 也可能是数字时间等其它类型, 所以这里需要一个从字符串到其它类型的转换过程, 而简单类型的目的就在于 MVC 进行模型绑定时可以直接根据名称从 ValueProvider 中获取到值(一个字符串), 然后通过类型转换器将该字符串转换成所需类型
下面就介绍一些理所当然的类型转换器:
数字: 下图是数字转换器的基类, 它的 CanConvertFrom 方法的实现直接硬编码了能够从 string 类型转换数字类型(不同数字类型有具体的实现, 这里不再介绍)
时间: 同样的时间类型也能从字符串转换
布尔: 布尔类型能够从字符串转换
为什么说理所当然? 因为在实际的开发中某 action 需要一个时间参数, 那么在表单中填写或者通过一些 js 组件选择一个日期, 然后提交到服务器这个填写的时间就是一个时间类型, 填写的数字也就是数字类型, 一切都是理所当然的但是打着没有魔法的目的, 需要知道的是, 哪怕最简单的布尔类型实际上也进行了转换工作, 下图就是 Boolean 转换器的转换代码:
在 MVC 中简单模型和复杂模型绑定的方式是不同的, 也很容易理解简单模型仅需要一个字符串就可以完成转换, 而复杂模型还需要更多的操作
在 ASP.NET MVC 中还有一种用法, 就是自定义将特殊格式的字符串转换成特定对象, 常用的例子就是坐标 (经纬度) 的转换, 下面将使用逗号分隔字符串的格式定义用户注册的 RegisterViewModel:
1 创建一个 RegisterViewModel 的转换器, 继承 TypeConverter 类型并重载 CanConvertFrom 及 ConvertFrom 方法即可:
2 使用 TypeConverter 特性在 RegisterViewModel 类型上使用该转换器:
3 通过 Postman 模拟请求:
注: model 为 action 参数名称
Action 能够正确的获取到数据:
小结
本篇内容主要是介绍了 ASP.NET MVC 中模型绑定的主要组件与概念, ValueProvider 提供数据 ModelMetadata 描述数据 ModelBinder 绑定数据, 绑定数据过程中不可或缺的数据验证转换功能分别对应了 ModelValidation 以及 TypeConverter 类型下一篇文章将介绍 ASP.NET MVC 在 Controller 执行时如何结合这些组件实现模型绑定的逻辑
PS. 由于篇幅较长所以将模型绑定解析的内容分为两篇, 下篇将尽快整理并发出祝各位元宵快乐(*^_^*)
参考:
- http://stephenwalther.com/archive/2009/02/25/asp-net-mvc-tip-49-use-the-linqbinarymodelbinder-in-your
- https://www.cnblogs.com/Cwj-XFH/p/5977508.html
来源: https://www.cnblogs.com/selimsong/p/8484482.html