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


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

    上一篇:http://www.cnblogs.com/RobbinHan/archive/2011/12/15/2289228.html 

    三.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

    总结:

    1. Model Validatoin 可以通过在Modol属性上加入*ValidationAttribute 和 实现IValidatableObject 接口来进行验证
    2. 通过对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,如果有人遇到请告诉我。


    转载请注明出处:http://www.cnblogs.com/RobbinHan/archive/2011/12/15/2289403.html

    本文作者: 十一月的雨 http://www.cnblogs.com/RobbinHan

  • 相关阅读:
    vue 之循环添加不同class
    小程序 之使用HMACSHA1算法加密报文
    微信小程序 之wx.getLocation()获取地理信息中的小坑
    js 时间戳与yyyy-mm-dd或yyyy-MM-dd HH-mm-ss互相转换
    小程序 之登录 wx.login()
    打开串口(COM)号大于9时报错
    linux的mysql权限错误导致看不到mysql数据库
    Nginx报错汇总
    获取磁盘总空间和剩余空间
    关于CoCreateInstance的0x800401f0问题
  • 原文地址:https://www.cnblogs.com/RobbinHan/p/2289403.html
Copyright © 2020-2023  润新知