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);
});
具体是怎么验证的我这里就忽略了.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构