深度解析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