ASP.NET MVC以ModelValidator为核心的Model验证体系: ModelValidatorProviders

前面篇文章我们分别介绍用真正用于实施Model验证的ModelValidator(《ASP.NET MVC以ModelValidator为核心的Model验证体系: ModelValidator》),以及用于提供ModelValidator的ModelValidatorProvider(《ASP.NET MVC以ModelValidator为核心的Model验证体系: ModelValidatorProvider》),那么对于ASP.NET MVC的Model验证体系来说,最终是通过怎样的方式对ModelValidatorProvider进行注册,又是如何利用它们来创建相应的ModelValidator来实施Model验证的呢?这就是本篇文章论述的重点。[本文已经同步到《How ASP.NET MVC Works?》中]

目录 一、ModelValidatorProviders 二、ModelValidator、ModelValidatorProvider和ModelValidatorProviders 三、CompositeModelValidator 四、实例演示:探测CompositeModelValidator采用的验证行为

一、ModelValidatorProviders

我们通过静态类型ModelValidatorProviders对ModelValidatorProvider进行注册。如下面的代码片断所示,ModelValidatorProviders具有一个静态只读属性Providers,其类型为ModelValidatorProviderCollection,表示注册的基于整个Web应用范围的ModelValidatorProvider列表。

   1: public static class ModelValidatorProviders
   2: {   
   3:     public static ModelValidatorProviderCollection Providers { get; }
   4: }
   5:  
   6: public class ModelValidatorProviderCollection : Collection<ModelValidatorProvider>
   7: {   
   8:     public ModelValidatorProviderCollection();
   9:     public ModelValidatorProviderCollection(IList<ModelValidatorProvider> list);
  10:     public IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);   
  11: }

值得一提的是,ModelValidatorProviderCollection定义了一个GetValidators方法用于返回一个通过集合中每个ModelValidatorProvider创建的ModelValidator集合。在这个方法中,指定的Model元数据和Controller上下文会被传入每个ModelValidatorProvider对象的GetValidators方法,得到的每个ModelValidator对象将会作为最终返回的ModelValidator集合的元素。

在默认的情况下,通过ModelValidatorProviders的Providers表示注册的ModelValidatorProvider列表会包含三个对象,对应着我们前面介绍的三种ModelValidatorProvider类型,即DataAnnotationsModelValidatorProvider、ClientDataTypeModelValidatorProvider和DataErrorInfoPropertyModelValidator。

如果我们需要添加一个自定义ModelValidatorProvider,可以直接将相应的对象添加到ModelValidatorProviders的Providers列表中。如果需要采用自定义ModelValidatorProvider来替换掉现有的ModelValidatorProvider,比如我们创建了一个扩展的DataAnnotationsModelValidatorProvider,还需要将现有的ModelValidatorProvider从该列表中移除。

实现在ModelValidatorProvider中的ModelValidator提供机制是基于Model元数据和Controller上下文的,实际上用于描述Model元数据的ModelMetadata类型同样定义了一个GetValidators方法用于根据指定的Controller上下文的所有ModelValidator对象。如下面的代码片断所示,该方法直接调用了通过ModelValidatorProviders的Providers属性表述的ModelValidatorProviderCollection对象的同名方法。

   1: public abstract class ModelValidator
   2: {
   3:     //其他成员
   4:     public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context)
   5:     {
   6:         return ModelValidatorProviders.Providers.GetValidators(this, context);
   7:     }
   8: }

二、ModelValidator、ModelValidatorProvider和ModelValidatorProviders

上面我们介绍用于进行Model验证的ModelValidator,用于提供ModelValidator的ModelValidatorProvider,以及用于注册ModelValidatorProvider的ModelValidatorProviders,整个ModelValidator的提供机制以此三类组件为核心,下图所示的UML体现了它们之间的关系。

三、CompositeModelValidator

虽然CompositeModelValidator仅仅是定义在程序集System.Web.Mvc.dll中的一个私有类型,但是它在ASP.NET MVC的Model验证系统中具有重要的地位,可以说真正用于Model验证的ModelValidator就是这么一个对象。从如下所以的成员定义代码并不能看出CompositeModelValidator有何特别之处。

   1: private class CompositeModelValidator : ModelValidator
   2: {
   3:     public CompositeModelValidator(ModelMetadata metadata, ControllerContext controllerContext);
   4:     public override IEnumerable<ModelValidationResult> Validate(object container);
   5: }

从其类型名称可以看出CompositeModelValidator实际上并不是一个真正对Model对象实施验证的ModelValidator,它是一系列ModelValidator的组合,它根据基于Model本身类型及其属性的Model元数据动态地获取相应的ModelValidator(通过调用ModelMetadata的GetValidators方法)对Model对象实施验证。

定义在Validate方法中的验证逻辑是这样的:CompositeModelValidator通过在构造函数中初始化的表示验证对象类型的Model元数据的ModelMetadata对象的Properties属性得到基于属性的Model元数据列表。然后遍历该列表的每个ModelMetadata对象,调用其GetValidators方法得到一组用于验证属性值得ModelValidator列表,然后使用该ModelValidator列表依次对相应的属性值进行验证,验证失败得到的ModelValidationResult对象被添加到最终返回的ModelValidationResult集合中。

只有在所有属性值都通过验证的情况下,CompositeModelValidator采用调用基于被验证类型Model元数据的ModelMetadata对象的GetValidators方法得到在类型级别ModelValidator列表对指定的数据对象实施验证,验证失败得到的ModelValidationResult对象被添加到最终返回的ModelValidationResult集合中。

抽象类ModelValidator具有一个静态的GetModelValidator方法根据指定的Model元数据和Controller上下文得到相应的ModelValidator对象。如下面的代码片断所示,该方法返回的正是一个CompositeModelValidator对象。

   1: public abstract class ModelValidator
   2: {
   3:     //其他成员
   4:     public static ModelValidator GetModelValidator(ModelMetadata metadata, ControllerContext context) 
   5:     {
   6:         return new CompositeModelValidator(metadata, context);
   7:     }
   8: }

四、实例演示:探测CompositeModelValidator采用的验证行为

为了使读者对CompositeModelValidator的验证逻辑具有一个深刻的理解,我们来演示一个具体的Model验证的实例。我们创建了如果一个名称为AlwaysFailsAttribute的验证特性。如下面的代码片断所示,重写的IsValid方法总是返回False,意味着针对数据的验证总是会失败。我们还重写了只读属性TypeId,让它能够真正能够唯一标识一个AlwaysFailsAttribute特性实例,具体原因我们会在本章后续部分讲述。

   1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Property)]
   2:  public class AlwaysFailsAttribute : ValidationAttribute
   3: {
   4:     private object typeId;
   5:     public override bool IsValid(object value)
   6:     {
   7:         return false;
   8:     }
   9:     public override object TypeId
  10:     {
  11:         get { return typeId ?? (typeId = new object()); }
  12:     }
  13: }

我们将AlwaysFailsAttribute应用到具有如下定义的表示联系人的Contact类型上。如下面的代码片断所示,我们在Contact和Address的类型和属性都应用了该特性,并且指定了相应的错误消息。

   1: [AlwaysFails(ErrorMessage = "Contact")]
   2: public class Contact
   3: {
   4:     [AlwaysFails(ErrorMessage = "Contact.Name")]
   5:     public string Name { get; set; }
   6:  
   7:     [AlwaysFails(ErrorMessage = "Contact.PhoneNo")]
   8:     public string PhoneNo { get; set; }
   9:  
  10:     [AlwaysFails(ErrorMessage = "Contact.EmailAddress")]
  11:     public string EmailAddress { get; set; }
  12:  
  13:     [AlwaysFails(ErrorMessage = "Contact.Address")]
  14:     public Address Address { get; set; }
  15: }
  16:  
  17: [AlwaysFails(ErrorMessage = "Address")]
  18: public class Address
  19: {
  20:     [AlwaysFails(ErrorMessage = "Address.Province")]
  21:     public string Province { get; set; }
  22:  
  23:     [AlwaysFails(ErrorMessage = "Address.City")]
  24:     public string City { get; set; }
  25:  
  26:     [AlwaysFails(ErrorMessage = "Address.District")]
  27:     public string District { get; set; }
  28:  
  29:     [AlwaysFails(ErrorMessage = "Address.Street")]
  30:     public string Street { get; set; }
  31: }

在一个通过Visual Studio的ASP.NET MVC项目模板创建的空Web应用中,我们创建了具有如下定义的默认HomeController类。在Action方法Index中,我们使用当前的ModelMetadataProvider创建了基于Contact类型的ModelMetadata,然后调用ModelValidator的静态方法GetValidator方法得到基于该ModelMetadata和ControllerContext的ModelValidator对象(一个CompositeModelValidator对象)。最后我们通过该ModelValidator对象来验证手工创建的Contact对象,并将得到的ModelValidationResult对象的MemberName和Message属性呈现出来。

   1: public class HomeController : Controller
   2: {
   3:     public void Index()
   4:     {
   5:         Address address = new Address
   6:         {
   7:             Province = "江苏",
   8:             City     = "苏州",
   9:             District = "工业园区",
  10:             Street   = "星湖街328号"
  11:         };
  12:         Contact contact = new Contact
  13:         {
  14:             Name         = "张三",
  15:             PhoneNo      = "123456789",
  16:             EmailAddress = "zhangsan@gmail.com",
  17:             Address      = address
  18:         };
  19:  
  20:         ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => contact, typeof(Contact));
  21:         ModelValidator validator = ModelValidator.GetModelValidator(metadata, ControllerContext);
  22:         foreach (ModelValidationResult result in validator.Validate(contact))
  23:         { 
  24:             Response.Write(string.Format("{0}: {1}<br/>", string.IsNullOrEmpty(result.MemberName)? "N/A": result.MemberName, result.Message));
  25:         }
  26:     }
  27: }

运行该程序后会在浏览器中得到如下所示的输出结果。这样的输出结果至少反映了两个问题,其一,CompositeModelValidator对数据的验证并不是递归进行的,因为只有应用在Contact属性上的验证特性参与了验证,而应用在Address类型属性上的验证特性则没有被使用;其二,在属性认证失败的情况下是不会进行基于类型的验证的,因为浏览器中并不存在应用在Contact类型上的验证特性对应的输出。

   1: Name        : Contact.Name
   2: PhoneNo     : Contact.PhoneNo
   3: EmailAddress: Contact.EmailAddress
   4: Address     : Contact.Address
   5: Address     : Address

上面的输出结果还反映了另外一个细节,针对某个属性的ModelValidator列表会同时包含应用在属性和属性对应类型的验证特性生成的ModelValidator。输出的最后两个ModelValidationResult都是针对Contact的Address属性的,分别对应着应用在Contact的Address属性和Address类型上的两个AlwaysFailsAttribute特性。现在我们按照如下的方式将应用在Contact的四个属性以及Address类型上的AlwaysFailsAttribute特性注册掉,只保留应用在Contact类型的AlwaysFailsAttribute特性。

   1: [AlwaysFails(ErrorMessage = "Contact")]
   2: public class Contact
   3: {
   4:     //[AlwaysFails(ErrorMessage = "Contact.Name")]
   5:     public string Name { get; set; }
   6:  
   7:     //[AlwaysFails(ErrorMessage = "Contact.PhoneNo")]
   8:     public string PhoneNo { get; set; }
   9:  
  10:     //[AlwaysFails(ErrorMessage = "Contact.EmailAddress")]
  11:     public string EmailAddress { get; set; }
  12:  
  13:     //[AlwaysFails(ErrorMessage = "Contact.Address")]
  14:     public Address Address { get; set; }
  15: }
  16:  
  17: //[AlwaysFails(ErrorMessage = "Address")]
  18: public class Address
  19: {
  20:     //省略成员
  21: }

在此运行我们的程序将会在浏览器中得到如下的输出结果。不难看出输出的ModelValidationResult对应于着应用在Contact类型上的AlwaysFailsAttribute特性,这充分反映了上面所说的:基于类型的验证只有在基于属性的验证失败的情况下才会进行。

   1: N/A: Contact

ASP.NET MVC以ModelValidator为核心的Model验证体系: ModelValidator ASP.NET MVC以ModelValidator为核心的Model验证体系: ModelValidatorProvider ASP.NET MVC以ModelValidator为核心的Model验证体系: ModelValidatorProviders

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Go基础系列:channel入门

channel用于goroutines之间的通信,让它们之间可以进行数据交换。像管道一样,一个goroutine_A向channel_A中放数据,另一个goro...

15240
来自专栏hotqin888的专栏

用golang递归构建无限级树状目录json数据和数据库

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hotqin888/article/det...

36420
来自专栏生信宝典

Bookdown文档生成教程

bookdown是一款及其方便的编写技术文档或教材的工具,语法简洁,数据处理灵活。支持Rmarkdown或普通markdown通过pandoc软件转换为HTML...

64350
来自专栏每日一篇技术文章

Metal_入门02_带你走流程

Metal 系列教程 Metal_入门01_为什么要学习它 Metal_入门02_带你走流程

12710
来自专栏零基础使用Django2.0.1打造在线教育网站

零基础使用Django2.0.1打造在线教育网站(七):数据库字段的定义(上)

努力与运动兼备~有任何问题可以加我好友或者关注微信公众号,欢迎交流,我们一起进步!

38860
来自专栏生信技能树

linux命令行文本操作一文就够

主要是 awk/grep/sed这三驾马车,加上vi这个神器,最后辅助一些小工具,包括 wc,cat,diff,join,paste,cut,uniq 这里 简...

50990
来自专栏影子

Java解析OFFICE(word,excel,powerpoint)以及PDF的实现方案及开发中的点滴分享

667160
来自专栏琯琯博客

Yii2 学习笔记之验证规则

47360
来自专栏老马说编程

(64) 常见文件类型处理: 属性文件/CSV/EXCEL/HTML/压缩文件 / 计算机程序的思维逻辑

查看历史文章,请点击上方链接关注公众号。 对于处理文件,我们介绍了流的方式,57节介绍了字节流,58节介绍了字符流,同时,也介绍了比较底层的操作文件的方式,60...

43280
来自专栏逆向技术

PE格式第六讲,导出表

                PE格式第六讲,导出表 请注意,下方字数比较多,其实结构挺简单,但是你如果把博客内容弄明白了,对你受益匪浅,千万不要看到字...

19860

扫码关注云+社区

领取腾讯云代金券