Asp.net MVC源码分析--Model Validation(Server端)实现(2)

来源:岁月联盟 编辑:exp 时间:2011-12-31

前面我们介绍了Model Validation的用法,以及ValidateModel的方法实现,这一篇我们来详细学习一下DataAnnotationsModelValidatorProvider类的实现。

三.DataAnnotationsModelValidatorProvider类详解
1.AttributeFactories对象
首先在这个类中可以看到在初始化时创建了AttributeFactories对象(Dictionary),  这个集合包含了系统内置一些验证规则。
 1         internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>() {
 2             {
 3                 typeof(RangeAttribute),
 4                 (metadata, context, attribute) => new RangeAttributeAdapter(metadata, context, (RangeAttribute)attribute)
 5             },
 6             {
 7                 typeof(RegularExpressionAttribute),
 8                 (metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute)
 9             },
10             {
11                 typeof(RequiredAttribute),
12                 (metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute)
13             },
14             {
15                 typeof(StringLengthAttribute),
16                 (metadata, context, attribute) => new StringLengthAttributeAdapter(metadata, context, (StringLengthAttribute)attribute)
17             },
18         }
19 }
复制代码
2.ValidationAttribte 的 Adapter 设计模式应用
这里特别需要注意的是MVC利用了*AttributeAdapter 把 ValidationAttribte 的GetValidationResult方法和 ModelValidator.Validate方法作了一个适配(这里用到Adapter模式)请看RangeAttributeAdapter/RegularExpressionAttributeAdapter/RequiredAttributeAdapter/StringLengthAttributeAdapter
请参照DataAnnotationsModelValidator.Validate 方法源码,第7行代码,就是在这里进行了适配的工作。
 1  public override IEnumerable<ModelValidationResult> Validate(object container) {
 2             // Per the WCF RIA Services team, instance can never be null (if you have
 3 // no parent, you pass yourself for the "instance" parameter).
 4             ValidationContext context = new ValidationContext(container ?? Metadata.Model, null, null);
 5             context.DisplayName = Metadata.GetDisplayName();
 6
 7             ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context);
 8             if (result != ValidationResult.Success) {
 9                 yield return new ModelValidationResult {
10                     Message = result.ErrorMessage
11                 };
12             }
13         }
复制代码
3.获取ModelValidator对象集合
接下来我们来分析一下DataAnnotationsModelValidatorProvider.GetValidators 方法的实现
 1  protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) {
 2             _adaptersLock.EnterReadLock();
 3
 4             try {
 5                 List<ModelValidator> results = new List<ModelValidator>();
 6
 7                 // Add an implied [Required] attribute for any non-nullable value type,
 8 // unless they've configured us not to do that.
 9                 if (AddImplicitRequiredAttributeForValueTypes &&
10                         metadata.IsRequired &&
11                         !attributes.Any(a => a is RequiredAttribute)) {
12                     attributes = attributes.Concat(new[] { new RequiredAttribute() });
13                 }
14
15                 // Produce a validator for each validation attribute we find
16                 foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>()) {
17                     DataAnnotationsModelValidationFactory factory;
18                     if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory)) {
19                         factory = DefaultAttributeFactory;
20                     }
21                     results.Add(factory(metadata, context, attribute));
22                 }
23
24                 // Produce a validator if the type supports IValidatableObject
25                 if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType)) {
26                     DataAnnotationsValidatableObjectAdapterFactory factory;
27                     if (!ValidatableFactories.TryGetValue(metadata.ModelType, out factory)) {
28                         factory = DefaultValidatableFactory;
29                     }
30                     results.Add(factory(metadata, context));
31                 }
32
33                 return results;
34             }
35             finally {
36                 _adaptersLock.ExitReadLock();
37             }
38         }
复制代码
我们看到从16-22行, MVC会从属性的Attributes中找到与AttributeFactories匹配的DataAnnotationsModelValidationFactory, 并调用返回ModelValidator对象,但如果没有从AttributeFactories找到匹配的对象,则使用DefaultAttributeFactory 委托创建一个ModelValidator对象。
DefaultAttributeFactory内部实现是创建一个DataAnnotationsModelValidator对象在构造时传入ValidationAttribute, 实际上这个类也是RangeAttributeAdapter等*AttributeAdapter类的基类,它们初始化的逻辑也是一样的。
1   internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory =
2             (metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute);
复制代码
4.IValidatableObject接口
最后我们看一下DataAnnotationsModelValidatorProvider.GetValidators 方法第25-30行代码. 
在这个方法中同样的我们发现MVC利用ValidatableObjectAdapter适配器连接ModelValidator.Validate方法 和 IValidatableObject.Validate接口.
BTW:利用IValidatableObject接口可以实现验证模型的各个属性之间的逻辑关系.
 可以参考另一篇文章:http://www.cnblogs.com/bjs007/archive/2011/01/27/1946419.html
总结:
• Model Validatoin 可以通过在Modol属性上加入*ValidationAttribute 和 实现IValidatableObject 接口来进行验证
• 通过对DataAnnotationsModelValidatorProvider.GetValidators 方法的分析我们得出:
如果Model中的属性如果是复杂对象时,即使子对象中标记了*ValidationAttribute也是不会验证的。
        例如下面的代码:LogOnModel.ChangePassword 对象中的属性虽然标记了[Required]但是还是不会验证的。
 1  public class ChangePasswordModel
 2     {
 3         [Required]
 4         [DataType(DataType.Password)]
 5         [Display(Name = "Current password")]
 6         public string OldPassword { get; set; }
 7
 8         [Required]
 9         [ValidatePasswordLength]
10         [DataType(DataType.Password)]
11         [Display(Name = "New password")]
12         public string NewPassword { get; set; }
13
14         [DataType(DataType.Password)]
15         [Display(Name = "Confirm new password")]
16         [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
17         public string ConfirmPassword { get; set; }
18     }
19
20     public class LogOnModel : IValidatableObject
21     {
22         [Required]      
23         [Display(Name = "User name")]     
24         public string UserName { get; set; }
25         [Required]
26         [Display(Name = "User age")]
27         public string Age { get; set; }
28         [Required]
29         [DataType(DataType.Password)]
30         [Display(Name = "Password")]
31         public string Password { get; set; }      
32         [Display(Name = "Remember me?")]
33         public bool RememberMe { get; set; }
34         [Display(Name = "ChangePassword")]
35         //[Required]
36         public ChangePasswordModel ChangePassword { get; set; }       
37
38         public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
39         {
40             return Enumerable.Empty<ValidationResult>();
41         }
42     }
复制代码
疑问
另外有一点迷惑的是如果,如果把LogOnModel.ChangePassword属性是标记上[Required],而不从前台传入这个对象的话,所有LogOnModel属性上的验证都会失败,这一点不知道是不是Asp.net MVC 的bug,如果有人遇到请告诉我。

 摘自 十一月的雨