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

2022-05-09 13:06:40 浏览数 (1)

在《ASP.NET MVC以ModelValidator为核心的Model验证体系: ModelValidator》中我们介绍了ASP.NET MVC用于Model验证的四种ModelValidator,那么这些ModelValidator是如何被创建的呢?ASP.NET MVC的很多组件(比如ModelBinder和Filter)都采用了基于Provider的提供机制,这篇文章为你讲述这些ModelValidator对应的ModelValidatorProvider。[本文已经同步到《How ASP.NET MVC Works?》中]

目录 一、ModelValidatorProvider 二、DataAnnotationsModelValidator 三、ClientDataTypeModelValidatorProvider 四、DataErrorInfoModelValidatorProvider

一、ModelValidatorProvider

我们通过注册ModelValidatorProvider来创建相应的ModelValidator,所有的ModelValidatorProvider直接或者间接地继承类型ModelValidatorProvider。如下面的代码片断所示,ModelValidator的提供实现在抽象方法GetValidators种,返回的是一个ModelValidator集合。

代码语言:javascript复制
1: public abstract class ModelValidatorProvider

       2: {  

       3:     public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ModelBindingExecutionContext context);

       4: }

由于ValueProvider提供的数据值仅限于简单类型,所以针对复杂类型的Model绑定采用一个递归的过程对作为Model对象的所有属性进行绑定。Model验证可以看成是Model绑定的后续环节,它对绑定的数据实施验证,所以Model验证也是一个递归的过程,它采用基于属性的验证规则对绑定的属性值实施验证。GetValidators方法具有两个参数,类型ModelMetadata的metadata参数用于或者相应的验证规则,而参数context则是表示当前Model绑定上下文的ModelBindingExecutionContext对象。

二、DataAnnotationsModelValidator

上面我们提到过的针对数据标注特性验证方式的DataAnnotationsModelValidator对应的ModelValidatorProvider类型为DataAnnotationsModelValidatorProvider。如下面的代码片断所示,DataAnnotationsModelValidatorProvider继承自另一个抽象类型AssociatedValidatorProvider。

代码语言:javascript复制
1: public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider

       2: {

       3:     //其他成员

       4:     public DataAnnotationsModelValidatorProvider();   

       5:     protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context,IEnumerable<Attribute> attributes);    

       6: }

类型名称AssociatedValidatorProvider中所谓的“关联(Association)”实际上代表的是关联的特性列表,即它根据从Model元数据中得到的用于定义验证规则的特性列表来提供相应的ModelValidator。如下面的代码片断所示,AssociatedValidatorProvider定义一个受保护的虚方法GetTypeDescriptor用于获取指定类型的描述对象(其类型实现了接口ICustomTypeDescriptor)。被解析出来的关联特性最终传入抽象的GetValidators方法实现了对ModelValidator的提供,而DataAnnotationsModelValidatorProvider正是实现了这个方法来创建相应的DataAnnotationsModelValidator列表。

代码语言:javascript复制
1: public abstract class AssociatedValidatorProvider : ModelValidatorProvider

       2: {

       3:     protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type);

       4:     public sealed override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);

       5:     protected abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes);   

       6: }

在被重写的GetValidators方法中,如果当前Model元数据是基于某个属性的(表示容器类型的ContainerType不会Null并且具有属性名称),在调用GetTypeDescriptor方法获取容器类型描述对象,进而根据属性类型得到用于描述属性的PropertyDescriptor对象,最终通过该描述对象得到应用在对应属性上的所有特性并调用抽象方法GetValidators返回基于属性的ModelValidator列表。对于非属性Model元数据,在直接调用GetTypeDescriptor方法得到Model类型描述对象,进而获取应用在Model类型上的所有特性并传入抽象方法GetValidators实现对针对Model类型的ModelValidator的提供。

三、ClientDataTypeModelValidatorProvider

针对数值和日期类型客户端验证的NumericModelValidator和DateModelValidator最终是通过具有如下定义的ClientDataTypeModelValidatorProvider来提供的。在GetValidators方法中,它会根据指定的Model元数据判断是否属于数值类型/DateTime类型,如果是则直接返回一个包含单个NumericModelValidator/DateModelValidator对象的ModelValidator集合。在这里被视为数值的数据类型包括byte,、sbyte、short,、ushort、int、uint,long,、ulong、float、double,和decimal等。

代码语言:javascript复制
1: public class ClientDataTypeModelValidatorProvider : ModelValidatorProvider

       2: {  

       3:     public ClientDataTypeModelValidatorProvider();   

       4:     public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);

       5: }

四、DataErrorInfoModelValidatorProvider

两个具体的DataErrorInfoModelValidator,即DataErrorInfoClassModelValidator和DataErrorInfoPropertyModelValidator最终是通过具有如下定义的DataErrorInfoModelValidatorProvider来提供的。对于GetValidators的具体实现来说,如果Model类型实现了IDataErrorInfo接口,会基于制定的Model元数据和Controller上下文创建一个DataErrorInfoClassModelValidator对象置于返回的ModelValidtor集合中。对于基于属性的Model元数据来说,如果其容器类型实现了IDataErrorInfo接口,该方法返回的ModelValidtor集合中还会包含一个基于指定Model元数据和Controller上下文创建的DataErrorInfoPropertyModelValidator对象。

代码语言:javascript复制
1: public class DataErrorInfoModelValidatorProvider : ModelValidatorProvider

       2: {

       3:     public DataErrorInfoModelValidatorProvider();

       4:     public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);  

       5: }

为了让读者更好地了解DataErrorInfoModelValidator的验证规则,以及定义在DataErrorInfoPropertyModelValidator中针对它的提供机制,我们来演示一个简单的实例。在通过Visual Studio的ASP.NET MVC项目模板创建的空Web应用中我们定义了如下一个实现了IDataErrorInfo接口的Contact类型。

代码语言:javascript复制
1: public class Contact: IDataErrorInfo

       2: {

       3:     public string Error

       4:     {

       5:         get { return "无效联系人!";}

       6:     }

       7:     public string this[string columnName]

       8:     {

       9:         get

      10:         {

      11:             switch (columnName)

      12:             {

      13:                 case "Name"           : return "姓名是必需的!";

      14:                 case "PhoneNo"        : return "电话号码格式错误!";

      15:                 case "EmailAdderss"   : return "无效的电子邮箱地址!";

      16:                 default               : return null;

      17:             }

      18:         }

      19:     }

      20:     public string Name { get; set; }

      21:     public string PhoneNo { get; set; }

      22:     public string EmailAdderss { get; set; }

      23: }

然后创建了如下一个默认的HomeController类。在Action方法Index中,我们通过DataErrorInfoModelValidatorProvider根据Contact类型极其属性的Model元数据创建了一个ModelValidator列表,然后使用这个列表中的每个具体的ModelValidator对一个Contact对象实施验证,并将ModelValidator的类型和作为验证结果的ModelValidationResult对象的ErrorMessage属性呈现出来。

代码语言:javascript复制
1: public class HomeController : Controller

       2: {

       3:     public void Index()

       4:     {

       5:         Contact contact = new Contact();

       6:         ModelValidatorProvider validatorProvider = new DataErrorInfoModelValidatorProvider();

       7:         foreach (var validator in GetValidators(contact, validatorProvider))

       8:         {                

       9:             var validationResults = validator.Validate(contact);

      10:             if (validationResults.Any())

      11:             {

      12:                 Response.Write(validator.GetType().Name   "<br/>");

      13:             }

      14:             foreach(var validationResult in validationResults)

      15:             {

      16:                 Response.Write(validationResult.Message   "<br>");

      17:             }

      18:         }

      19:     }

      20:  

      21:     private IEnumerable<ModelValidator> GetValidators(Contact contact, ModelValidatorProvider validatorProvider)

      22:     {

      23:         ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => contact, typeof(Contact));

      24:         foreach (var validator in validatorProvider.GetValidators(metadata, ControllerContext))

      25:         {

      26:             yield return validator;

      27:         }

      28:  

      29:         foreach (var propertyMetadata in metadata.Properties)

      30:         {

      31:             foreach (var validator in validatorProvider.GetValidators(propertyMetadata, ControllerContext))

      32:             {

      33:                 yield return validator;

      34:             }

      35:         }

      36:     }

      37: }

上面的程序运行之后会在浏览器中呈现出如下所示的输出结果,从中可以看到针对Contact类型的Model元数据创建的是一个DataErrorInfoClassModelValidator,而针对其属性的Model元数据创建的则是一个DataErrorInfoPropertyModelValidator对象。前者对Contact对象本身实施验证,并将Error属性作为验证结果的错误消息;后者针对应的属性实施验证,验证结果的错误消息来源于将属性名称作为索引的值。

代码语言:javascript复制
1: DataErrorInfoClassModelValidator

       2: 无效联系人!

       3: DataErrorInfoPropertyModelValidator

       4: 姓名是必需的!

       5: DataErrorInfoPropertyModelValidator

       6: 电话号码格式错误!

       7: DataErrorInfoPropertyModelValidator

       8: 无效的电子邮箱地址!

0 人点赞