前面文章介绍了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为Key、IModelBinder类型为值的字典接口以及集合接口,可以动态的根据ModelBinder的类型增减ModelBinder,除此之外还有一个DefaultBinder,在一般情况下其运行的实例如下:

  

  Controller中的ModelBinder字典包含了上面介绍的5个ModelBinder。更多关于自定义ModelBinder的内容可参考:https://www.cnblogs.com/Cwj-XFH/p/5977508.html

ValuePrivoder

  在前面的文章中介绍了ASP.NET MVC的模型绑定可以从Form Data、Query 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请求无论Header、Body等它实质上都是字符串,那么根据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

本文链接:http://www.cnblogs.com/selimsong/p/8484482.html

ASP.NET没有魔法——目录

ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(上篇)的更多相关文章

  1. ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(下篇)

    上一篇<ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(上篇)>文章介绍了ASP.NET MVC模型绑定的相关组件和概念,本章将介绍Controller在执行时是如何通过这 ...

  2. ASP.NET没有魔法——ASP.NET MVC 模型绑定

    在My Blog中已经有了文章管理功能,可以发布和修改文章,但是对于文章内容来说,这里缺少最重要的排版功能,如果没有排版的博客很大程度上是无法阅读的,由于文章是通过浏览器查看的,所以文章的排版其实与网 ...

  3. ASP.NET MVC模型绑定的6个建议(转载)

    ASP.NET MVC模型绑定的6个建议 发表于2011-08-03 10:25| 来源博客园| 31 条评论| 作者冠军 validationasp.netmvc.netasp 摘要:ASP.NET ...

  4. ASP.NET Core MVC 模型绑定用法及原理

    前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC ...

  5. asp.net Mvc 模型绑定项目过多会导致页面运行时间卡

    asp.net Mvc 模型绑定项目过多会导致页面运行时间卡的问题. 解决方式就是采用ModelView方式进行精简,已减少模型绑定及验证的时间.

  6. [转] ASP.NET MVC 模型绑定的功能和问题

    摘要:本文将与你深入探究 ASP.NET MVC 模型绑定子系统的核心部分,展示模型绑定框架的每一层并提供扩展模型绑定逻辑以满足应用程序需求的各种方法. 同时,你还会看到一些经常被忽视的模型绑定技术, ...

  7. ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

    对于Web应用来说,它的界面是由浏览器根据HTML代码及其引用的相关资源进行渲染后展示给用户的结果,换句话说Web应用的界面呈现工作是由浏览器完成的,Web应用的原理是通过Http协议从服务器上获取到 ...

  8. ASP.NET没有魔法——ASP.NET MVC Razor与View渲染 ASP.NET没有魔法——ASP.NET MVC界面美化及使用Bundle完成静态资源管理

    ASP.NET没有魔法——ASP.NET MVC Razor与View渲染   对于Web应用来说,它的界面是由浏览器根据HTML代码及其引用的相关资源进行渲染后展示给用户的结果,换句话说Web应用的 ...

  9. ASP.NET没有魔法——ASP.NET MVC 与数据库大集合

    ASP.NET没有魔法——ASP.NET与数据库 ASP.NET没有魔法——ASP.NET MVC 与数据库之MySQL ASP.NET没有魔法——ASP.NET MVC 与数据库之ORM ASP.N ...

随机推荐

  1. [转]CentOS Apache 性能调试!

    查看Apache的并发请求数及其TCP连接状态: netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 返回结果示例 ...

  2. Linux指令--ln

    ln是linux中又一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在 ...

  3. Spring MVC 配置文件dispatcher-servlet.xml 文件详解(转自 学无止境-yj)

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  4. cursor的moveToNext()与moveToFirst()

    String sql = "select count(*) as c from sqlite_master where type ='table' and name ='" + t ...

  5. Shell脚本中使用function(函数)示例

    这篇文章主要介绍了Shell脚本中使用function(函数)示例,本文着重讲解的是如何在shell脚本中使用自定义函数,并给出了两个例子,需要的朋友可以参考下   函数可以在shell script ...

  6. yum 安装zabbix2.4 /3.2.4

    yum 安装zabbix2.4 首先zabbix需要的环境是web环境,默认的是lamp或者lnmp,讲道理,要是使用tomcat也是可以的,不过实验没有成功 系统:centos6.8_x64 附yu ...

  7. Activity组件安全(下)

    什么是Activity劫持 简单的说就是APP正常的Activity界面被恶意攻击者替换上仿冒的恶意Activity界面进行攻击和非法用途.界面劫持攻击通常难被识别出来,其造成的后果不仅会给用户带来严 ...

  8. raid制作(转载)

    Dell r710服务器,有4块450G硬盘,默认做的RAID5.我们的目的是取其中3块硬盘做RAID5,留一块硬盘做热备. 在这里,我具体解释一下 ①4块硬盘做成RAID5 ②3块硬盘做RAID5, ...

  9. canvas将图片转为base64

    最简例子 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta ...

  10. MySQL死锁案例分析与解决方案

    MySQL死锁案例分析与解决方案 现象: 数据库查询: SQL语句分析:  mysql. 并发delete同一行记录,偶发死锁.   delete from x_table where id=?   ...