asp.net mvc源码分析-ModelValidatorProviders
在上篇文章asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证最后提到了ModelValidatorProviders ,这里我们以DataAnnotationsModelValidatorProvider来说说整过的验证过程。因为 DataAnnotationsModelValidatorProvider这个是我们平时用的最多的情况。其GetValidators的具体实现如 下:
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) { _adaptersLock.EnterReadLock(); try { List<ModelValidator> results = new List<ModelValidator>(); // Add an implied [Required] attribute for any non-nullable value type, // unless they've configured us not to do that. if (AddImplicitRequiredAttributeForValueTypes && metadata.IsRequired && !attributes.Any(a => a is RequiredAttribute)) { attributes = attributes.Concat(new[] { new RequiredAttribute() }); } // Produce a validator for each validation attribute we find foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>()) { DataAnnotationsModelValidationFactory factory; if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory)) { factory = DefaultAttributeFactory; } results.Add(factory(metadata, context, attribute)); } // Produce a validator if the type supports IValidatableObject if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType)) { DataAnnotationsValidatableObjectAdapterFactory factory; if (!ValidatableFactories.TryGetValue(metadata.ModelType, out factory)) { factory = DefaultValidatableFactory; } results.Add(factory(metadata, context)); } return results; } finally { _adaptersLock.ExitReadLock(); } }
首先从这里的特性取出是ValidationAttribute特性的attribute,然后根据attribute的type取出相应的DataAnnotationsModelValidationFactory,其中以下4个特性和其DataAnnotationsModelValidationFactory返回的ModelValidator实例一一对应:
RangeAttribute->RangeAttributeAdapter
RegularExpressionAttribute->RegularExpressionAttributeAdapter
RequiredAttribute->RequiredAttributeAdapter
StringLengthAttribute->StringLengthAttributeAdapter
这里的4个AttributeAdapter都继承于
DataAnnotationsModelValidator<TAttribute> ->
DataAnnotationsModelValidator ->ModelValidator.
如果没有取消相应的DataAnnotationsModelValidationFactory就调用默认的 DefaultValidatableFactory,它返回一个ValidatableObjectAdapter的一个实例;然后依次调用它们的 Validate方法。以上面4个attribute为例,它们的Validate方法默认的实现在 DataAnnotationsModelValidator中的Validate方法:
public override IEnumerable<ModelValidationResult> Validate(object container) {
// Per the WCF RIA Services team, instance can never be null (if you have
// no parent, you pass yourself for the "instance" parameter).
ValidationContext context = new ValidationContext(container ?? Metadata.Model, null, null);
context.DisplayName = Metadata.GetDisplayName();
ValidationResult result =
Attribute.GetValidationResult(Metadata.Model, context);
if (result != ValidationResult.Success) {
yield return new ModelValidationResult {
Message = result.ErrorMessage
};
}
}
调用每个Attribute的GetValidationResult方法。验证失败就会返 回一个ModelValidationResult实例。到这里我们知道DataAnnotationsModelValidator的Validate 方法是怎么调用delete,但是这里类里面还有一个GetClientValidationRules方法,它好像是一个客户端的验证啊。调用 attribute的GetClientValidationRules方法。该方法是在哪里调用的了。我们有时候在view里面有这样的方 法 @Html.ValidationMessageFor(model => model.UserName)。
ValidationMessage方法中有这么一段
if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled) {
builder.MergeAttribute("data-valmsg-for", modelName);
builder.MergeAttribute("data-valmsg-replace",
replaceValidationMessageContents.ToString().ToLowerInvariant());
}
else {
FieldValidationMetadata fieldMetadata = ApplyFieldValidationMetadata(htmlHelper, modelMetadata, modelName);
// rules will already have been written to the metadata object
fieldMetadata.ReplaceValidationMessageContents =
replaceValidationMessageContents; // only replace contents if no
explicit message was specified
// client validation always requires an ID
builder.GenerateId(modelName + "_validationMessage");
fieldMetadata.ValidationMessageId = builder.Attributes["id"];
}
最终会调用一个ApplyFieldValidationMetadata方法,不过默认的UnobtrusiveJavaScriptEnabled为true。
private static FieldValidationMetadata
ApplyFieldValidationMetadata(HtmlHelper htmlHelper, ModelMetadata
modelMetadata, string modelName) {
FormContext formContext = htmlHelper.ViewContext.FormContext;
FieldValidationMetadata fieldMetadata =
formContext.GetValidationMetadataForField(modelName, true /*
createIfNotFound */);
// write rules to context object
IEnumerable<ModelValidator> validators =
ModelValidatorProviders.Providers.GetValidators(modelMetadata,
htmlHelper.ViewContext);
foreach (ModelClientValidationRule rule in validators.SelectMany(v => v.GetClientValidationRules())) {
fieldMetadata.ValidationRules.Add(rule);
}
return fieldMetadata;
}
这里会依次调用我们ModelValidator的GetClientValidationRules方法。
例如我们的代码调用如下:
如果UnobtrusiveJavaScriptEnabled为true下的html如下:
UnobtrusiveJavaScriptEnabled为false的html如下:
我们以UnobtrusiveJavaScriptEnabled为true来说说客服端验证。在生成的html中data-val="true"表示要启用客户端验证,data-val-表示我们要验证的属性。负责验证的js是jquery.validate.unobtrusive.js,里面有这么一句:
$(selector).find(":input[data-val=true]").each(function () {
$jQval.unobtrusive.parseElement(this, true);
});
具体是怎么验证的我这里就忽略了.