ASP.NET MVC Model验证-ModelValidatorProviders
《ASP.NET MVC Model验证-ModelValidator》介绍用真正用于实施Model验证的ModelValidator,以及用于提供ModelValidator的ModelValidatorProvider《ASP.NET MVC Model验证-ModelValidatorProvider》, 那么对于ASP.NET MVC的Model验证体系来说,最终是通过怎样的方式对ModelValidatorProvider进行注册,又是如何利用它们来创建相应的 ModelValidator来实施Model验证的呢?这就是本篇文章论述的重点。
一、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
作者:Artech
出处:http://artech.cnblogs.com/
加支付宝好友偷能量挖...