深度解析MVC3中的ModelValidator及相关unobtrusiveJs的验证(一)

 

 

ModelValidator系统预置的ModelValidatorProvider都存放在ModelValidatorPrividers.Providers集合中,有:

1.DataAnnotationModelValidatorProvider:用于注解标识属性的验证器功能类。

2.DataErrorInfoModelValidatorProvider

3.ClientDataTypeModelValidatorProvider

4.EmptyModelValidatorProvider

 

下面是类的继承结构:

<![if !vml]><![endif]>

 

验证机制和过程:

系统调用每个ModelValidatorProvider.GetValidators(metadata, controllerContext)方法来获取他们内部的ModelValidator集合(IEnumerable<ModelValidator>),

将这些集合组合成一个集合,然后遍历这个集合逐个调用每个ModelValidator的Validate方法,Validate返回一个代表验证结果的ModelValidateResult类,验证过程

结束。过程是挺简单的。

 

 

下面是DataAnnotationModelValidatorProvider的实现过程:

1、DataAnnotationModelValidatorProvider类内部并没有实现ModelValidatorProvider.GetValidators方法,该方法是在AssociatedValidatorProvider中实现的,AssociatedValidatorProvider.GetValidators中会

   判断当前要验证的Model是类还是类的成员属性,如果是类,获取类的标识属性集合,如果是属性,获取属性的标识属性集合。

2、收集完了这些标识属性,然后AssociatedValidatorProvider.GetValidators会调用自己的另一个abstract重载,并为该方法传入Attribute属性集合。

   以下是AssociatedValidatorProvider中这两个函数的定义:

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

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

3、这个抽象GetValidators重载是在DataAnnotationModelValidatorProvider中实现的这个方法内部会获取DataAnnotationModelValidatorProvider内所有已预先注册的IEnumerable<ModelValidator>集合

   这些所有已预先注册的ModelValidator都是继承了DataAnnotationsModelValidator<TValidationAttribute>ValidatableObjectAdapter

   注意其中要继承DataAnnotationsModelValidator<TValidationAttribute>写自己的类构造函数必须要调用base里面唯一的那个构造函数。

   关于这些预先已注册的ModelValidator可以使用DataAnnotationModelValidatorProvider.RegisterAdapter等方法注册自己的ModelValidator实现后面再说。)

5、然后,逐个调用ModelValidator集合中每个ModelValidator的Validate方法,实际上是调用基类DataAnnotationsModelValidator.Validate或ValidatableObjectAdapter.Validate方法。

6、在基类的Validate方法内部,会调用ValidationAttribute.GetValidateResult返回一个ValidationResult(或调用IValidatableObject.Validate得到一个Validationresult),再转成ModelValidationResult。

6、Validate方法最终返回ModelValidationResult,这就是此Model的最终验证结果

 

 

关于注册过程

DataAnnotationModelValidatorProvider内部初始化的时候,默认会注册两种ModelValidator,一种是:<ValidationAttribute,ModelValidator工厂>, 另一种是:<IValidatableObject,ModelValidator工厂>

内部有四个ModelValidator存储结构:

1.DataAnnotationsModelValidationFactory 默认的是 () => new DataAnnotationModelValidator(metadata,controllerContext,attribute)

2.Dictionary<ValidationAttribute,DataAnnotationsModelValidationFactory>

3.DataAnnotationsValidatableObjectAdapterFactory 默认是 () => new ValidatableObjectAdapter(metadata,controllerContext)

4.Dictionary<IValidatableObject,DataAnnotationsValidatableObjectAdapterFactory>

优先都会查找Dictionary,Dictionary中没有,就用给默认的Factory

 

关于ModelClientValidationRule类:

它是客户端验证规则的存储体,HtmlHelper在生成标记的时候,会根据它的字段数据生成unobtrusive的验证标注(如:data-val-required="Age是必须填的字段"这种),将这些标注放入html标记中

它是通过DataAnnotationsModelValidator.GetClientValidationRules() 来获取一个IEnumerable<ModelClientValidationRule>,

对于像RemoteAttribute,CompareAttribute这些没有对应的像RemoteAttributeAdapter的对象,他们继承了IClientValidatable,将调用IClientValidatable.GetClientValidationRules()获取规则

 

实验: 自定义自己的验证Attribute(服务端验证)

1、定义一个继承ValidationAttribute的类A。里面有个IsValid,就是验证的核心方法。

2、定义一个继承DataAnnotationsModelValidator<TValidationAttribute>的类B,并将TValidationAttribute写成上面定义的A

3、在Global.cs中调用DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(A),typeof(B))注册适配器

4、OK服务端自定义验证写完了,这样就可以实现服务端验证了。在Action中,写 if(!ModelState.IsValid){  return View(); },记得,Action参数中要有这个模型参数

 

实验二:自定义自己的验证Attribute(客户端验证)

客户端验证,Html页面需要引用jquery的validation插件,微软的unobtrusive.js

1、在类A中加一些自定义客户端验证的参数字段/属性,如MaxLength,RegFormat,

2、在上面的第二步后,定义一个继承ModelClientValidationRule的类C

3、重写类B的GetClientValidationRules方法,返回一个实例化的C,构造时传入C中的刚才定义的字段/属性(format,errorMessage)。

4、在这个类C的构造函数中设置自己ValidateType,ErrorMessage,ValidationParameters。

   比如ValidationParameters设置为base.ValidationParameters["regformat"] = format,base.ErrorMesage=errorMessage,base.ValidationType="cellphone",ValidationType是适配器的名字。

    这几个format,errorMessage,cellphone都定义为构造函数参数。

3、和上面步骤一样,注册适配器。(Html.TextBoxFor 等方法调用时会将这些验证信息写入到Html标记中)

4、调用$.validator.addMethod('')注册一个方法,如下:

    jQuery.validator.addMethod("cellphoneFormat", function (value, element, params) {

        return new RegExp('^' + params.regformat + '$').test(value) && value.length >= params.length;

    }, '格式不正确');

5、调用$.validator.unobtrusive.addAdapter添加一个适配器,这个方法第三个参数中写自定义函数这个函数实现如何将适配器规则绑定到jquery validation上面,如下

    adapters.add('cellphone', ["regformat"], function (options) {

        setValidationValues(options, "cellphoneFormat", options.params);

    });

 

 

实验三自定义自己的IValidatableObject实现类和其对应的适配器

DataAnnotationsModelValidatorProvider.RegisterDefaultValidatableObjectAdapter(typeof(ValidatableObjectModelValidator));

 

public class ValidatableObjectModelValidator : ModelValidator

{

   public ValidatableObjectModelValidator(ModelMetadata metadata, ControllerContext controllerContext)

      : base(metadata, controllerContext)

   {

   }

 

   public override IEnumerable<ModelValidationResult> Validate(object container)

   {

      object model = base.Metadata.Model;

      if (model == null)

      {

         return Enumerable.Empty<ModelValidationResult>();

      }

      IValidatableObject validatableObject = model as IValidatableObject;

      if (validatableObject == null)

      {

         throw new InvalidOperationException("error");

      }

      ValidationContext validationContext = new ValidationContext(validatableObject, null, null);

      return validatableObject.Validate(validationContext).Select(x => new ModelValidationResult { Message = x.ErrorMessage, MemberName = string.Join(",", x.MemberNames) });

   }

}

 

public class AdapterTestClass : IValidatableObject

{

   public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)

   {

      AdapterTestClass c = validationContext.ObjectInstance as AdapterTestClass;

      if (string.IsNullOrEmpty(c.Name))

      {

         yield return new ValidationResult("?ó", new string[] { "Name" });

      }

      //return Enumerable.Empty<ValidationResult>();

   }

   public string Name { get; set; }

 

   public int Age { get; set; }

}

Action if(!ModelState.IsValid){  return View(); },记得Action参数加一个参数这个参数是这个AdapterTestClass

 

posted @ 2013-09-10 17:02  微软首席软件架构师  阅读(463)  评论(0编辑  收藏  举报