asp.net mvc源码分析-ModelValidatorProviders 客户端的验证
几年写过asp.net mvc源码分析-ModelValidatorProviders 当时主要是考虑mvc的流程对,客户端的验证也只是简单的提及了一下,现在我们来仔细看一下客户端的验证。
如图所示, 首先我们要知道这里的data-val这些属性是在哪里生成的?可以肯定是在mvc后台生成的,
@Html.PasswordFor(m => m.Password) 生成input
@Html.ValidationMessageFor(m => m.Password) 生成span
调用层级关系:
InputExtensions:
public static MvcHtmlString PasswordFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) {
return PasswordFor(htmlHelper, expression, null /* htmlAttributes */);
}
调用
public static MvcHtmlString PasswordFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes) {
if (expression == null) {
throw new ArgumentNullException("expression");
}
return PasswordHelper(htmlHelper,
ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData),
ExpressionHelper.GetExpressionText(expression),
null /* value */,
htmlAttributes);
}
调用
private static MvcHtmlString PasswordHelper(HtmlHelper htmlHelper, ModelMetadata metadata, string name, object value, IDictionary<string, object> htmlAttributes) {
return InputHelper(htmlHelper, InputType.Password, metadata, name, value, false /* useViewData */, false /* isChecked */, true /* setId */, true /* isExplicitValue */, htmlAttributes);
}
再调用
private static MvcHtmlString InputHelper(HtmlHelper htmlHelper, InputType inputType, ModelMetadata metadata, string name, object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, IDictionary<string, object> htmlAttributes) { string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name); if (String.IsNullOrEmpty(fullName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name"); } TagBuilder tagBuilder = new TagBuilder("input"); tagBuilder.MergeAttributes(htmlAttributes); tagBuilder.MergeAttribute("type", HtmlHelper.GetInputTypeString(inputType)); tagBuilder.MergeAttribute("name", fullName, true); string valueParameter = Convert.ToString(value, CultureInfo.CurrentCulture); bool usedModelState = false; switch (inputType) { case InputType.CheckBox: bool? modelStateWasChecked = htmlHelper.GetModelStateValue(fullName, typeof(bool)) as bool?; if (modelStateWasChecked.HasValue) { isChecked = modelStateWasChecked.Value; usedModelState = true; } goto case InputType.Radio; case InputType.Radio: if (!usedModelState) { string modelStateValue = htmlHelper.GetModelStateValue(fullName, typeof(string)) as string; if (modelStateValue != null) { isChecked = String.Equals(modelStateValue, valueParameter, StringComparison.Ordinal); usedModelState = true; } } if (!usedModelState && useViewData) { isChecked = htmlHelper.EvalBoolean(fullName); } if (isChecked) { tagBuilder.MergeAttribute("checked", "checked"); } tagBuilder.MergeAttribute("value", valueParameter, isExplicitValue); break; case InputType.Password: if (value != null) { tagBuilder.MergeAttribute("value", valueParameter, isExplicitValue); } break; default: string attemptedValue = (string)htmlHelper.GetModelStateValue(fullName, typeof(string)); tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(fullName) : valueParameter), isExplicitValue); break; } if (setId) { tagBuilder.GenerateId(fullName); } // If there are any errors for a named field, we add the css attribute. ModelState modelState; if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState)) { if (modelState.Errors.Count > 0) { tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName); } } tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata)); if (inputType == InputType.CheckBox) { // Render an additional <input type="hidden".../> for checkboxes. This // addresses scenarios where unchecked checkboxes are not sent in the request. // Sending a hidden input makes it possible to know that the checkbox was present // on the page when the request was submitted. StringBuilder inputItemBuilder = new StringBuilder(); inputItemBuilder.Append(tagBuilder.ToString(TagRenderMode.SelfClosing)); TagBuilder hiddenInput = new TagBuilder("input"); hiddenInput.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden)); hiddenInput.MergeAttribute("name", fullName); hiddenInput.MergeAttribute("value", "false"); inputItemBuilder.Append(hiddenInput.ToString(TagRenderMode.SelfClosing)); return MvcHtmlString.Create(inputItemBuilder.ToString()); } return tagBuilder.ToMvcHtmlString(TagRenderMode.SelfClosing); }
这个方法有个 TagBuilder tagBuilder = new TagBuilder("input"); 就是负者生成我们客户端的input控件,默认有 type,name,id属性,id属性的生成由以下code:
if (setId) {
tagBuilder.GenerateId(fullName);
}
整个方法的核心code 在 tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
public IDictionary<string, object> GetUnobtrusiveValidationAttributes(string name, ModelMetadata metadata) { Dictionary<string, object> results = new Dictionary<string, object>(); // The ordering of these 3 checks (and the early exits) is for performance reasons. if (!ViewContext.UnobtrusiveJavaScriptEnabled) { return results; } FormContext formContext = ViewContext.GetFormContextForClientValidation(); if (formContext == null) { return results; } string fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name); if (formContext.RenderedField(fullName)) { return results; } formContext.RenderedField(fullName, true); IEnumerable<ModelClientValidationRule> clientRules = ClientValidationRuleFactory(name, metadata); bool renderedRules = false; foreach (ModelClientValidationRule rule in clientRules) { renderedRules = true; string ruleName = "data-val-" + rule.ValidationType; ValidateUnobtrusiveValidationRule(rule, results, ruleName); results.Add(ruleName, HttpUtility.HtmlEncode(rule.ErrorMessage ?? String.Empty)); ruleName += "-"; foreach (var kvp in rule.ValidationParameters) { results.Add(ruleName + kvp.Key, kvp.Value ?? String.Empty); } } if (renderedRules) { results.Add("data-val", "true"); } return results; }
该方法首先检查 UnobtrusiveJavaScriptEnabled是否为true,FormContext是否存在, if (formContext.RenderedField(fullName)) 该控件是否已被Render。而这个方法的核心是
IEnumerable<ModelClientValidationRule> clientRules = ClientValidationRuleFactory(name, metadata);
获取ModelClientValidationRule集合,就开始追加属性了
foreach (ModelClientValidationRule rule in clientRules) {
renderedRules = true;
string ruleName = "data-val-" + rule.ValidationType; //获取客户端属性前缀
ValidateUnobtrusiveValidationRule(rule, results, ruleName);//验证客户端属性是否合法,不能有大写字母
results.Add(ruleName, HttpUtility.HtmlEncode(rule.ErrorMessage ?? String.Empty)); //追加errormessage的属性
ruleName += "-";
foreach (var kvp in rule.ValidationParameters) {
results.Add(ruleName + kvp.Key, kvp.Value ?? String.Empty); //追加参数属性, 如data-val-mulregular-minmatchno="3"
}
}
if (renderedRules) {
results.Add("data-val", "true"); //追加是否启用客户端验证
}
public HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection) { if (viewContext == null) { throw new ArgumentNullException("viewContext"); } if (viewDataContainer == null) { throw new ArgumentNullException("viewDataContainer"); } if (routeCollection == null) { throw new ArgumentNullException("routeCollection"); } ViewContext = viewContext; ViewDataContainer = viewDataContainer; RouteCollection = routeCollection; ClientValidationRuleFactory = (name, metadata) => ModelValidatorProviders.Providers.GetValidators(metadata ?? ModelMetadata.FromStringExpression(name, ViewData), ViewContext).SelectMany(v => v.GetClientValidationRules()); }
mvc中很多code 都是采用Factory和Providers来实现的。这里的metadata一般都是有值的并且是System.Web.Mvc.DataAnnotationsModelMetadata类型。默认有DataAnnotationsModelValidatorProvider,DataErrorInfoModelValidatorProvider,ClientDataTypeModelValidatorProvider
namespace System.Web.Mvc { public static class ModelValidatorProviders { private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection() { new DataAnnotationsModelValidatorProvider(), new DataErrorInfoModelValidatorProvider(), new ClientDataTypeModelValidatorProvider() }; public static ModelValidatorProviderCollection Providers { get { return _providers; } } } }
我们平时用的最多的应该是DataAnnotationsModelValidatorProvider,所以我们来看看
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* **************************************************************************** * * Copyright (c) Microsoft Corporation. All rights reserved. * * This software is subject to the Microsoft Public License (Ms-PL). * A copy of the license can be found in the license.htm file included * in this distribution. * * You must not remove this notice, or any other, from this software. * * ***************************************************************************/ namespace System.Web.Mvc { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Reflection; using System.Threading; using System.Web.Mvc.Resources; // A factory for validators based on ValidationAttribute public delegate ModelValidator DataAnnotationsModelValidationFactory(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute); // A factory for validators based on IValidatableObject public delegate ModelValidator DataAnnotationsValidatableObjectAdapterFactory(ModelMetadata metadata, ControllerContext context); /// <summary> /// An implementation of <see cref="ModelValidatorProvider"/> which providers validators /// for attributes which derive from <see cref="ValidationAttribute"/>. It also provides /// a validator for types which implement <see cref="IValidatableObject"/>. To support /// client side validation, you can either register adapters through the static methods /// on this class, or by having your validation attributes implement /// <see cref="IClientValidatable"/>. The logic to support IClientValidatable /// is implemented in <see cref="DataAnnotationsModelValidator"/>. /// </summary> public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider { private static bool _addImplicitRequiredAttributeForValueTypes = true; private static ReaderWriterLockSlim _adaptersLock = new ReaderWriterLockSlim(); // Factories for validation attributes internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory = (metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute); internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>() { { typeof(RangeAttribute), (metadata, context, attribute) => new RangeAttributeAdapter(metadata, context, (RangeAttribute)attribute) }, { typeof(RegularExpressionAttribute), (metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute) }, { typeof(RequiredAttribute), (metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute) }, { typeof(StringLengthAttribute), (metadata, context, attribute) => new StringLengthAttributeAdapter(metadata, context, (StringLengthAttribute)attribute) }, }; // Factories for IValidatableObject models internal static DataAnnotationsValidatableObjectAdapterFactory DefaultValidatableFactory = (metadata, context) => new ValidatableObjectAdapter(metadata, context); internal static Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory> ValidatableFactories = new Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory>(); public static bool AddImplicitRequiredAttributeForValueTypes { get { return _addImplicitRequiredAttributeForValueTypes; } set { _addImplicitRequiredAttributeForValueTypes = value; } } 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(); } } #region Validation attribute adapter registration public static void RegisterAdapter(Type attributeType, Type adapterType) { ValidateAttributeType(attributeType); ValidateAttributeAdapterType(adapterType); ConstructorInfo constructor = GetAttributeAdapterConstructor(attributeType, adapterType); _adaptersLock.EnterWriteLock(); try { AttributeFactories[attributeType] = (metadata, context, attribute) => (ModelValidator)constructor.Invoke(new object[] { metadata, context, attribute }); } finally { _adaptersLock.ExitWriteLock(); } } public static void RegisterAdapterFactory(Type attributeType, DataAnnotationsModelValidationFactory factory) { ValidateAttributeType(attributeType); ValidateAttributeFactory(factory); _adaptersLock.EnterWriteLock(); try { AttributeFactories[attributeType] = factory; } finally { _adaptersLock.ExitWriteLock(); } } public static void RegisterDefaultAdapter(Type adapterType) { ValidateAttributeAdapterType(adapterType); ConstructorInfo constructor = GetAttributeAdapterConstructor(typeof(ValidationAttribute), adapterType); DefaultAttributeFactory = (metadata, context, attribute) => (ModelValidator)constructor.Invoke(new object[] { metadata, context, attribute }); } public static void RegisterDefaultAdapterFactory(DataAnnotationsModelValidationFactory factory) { ValidateAttributeFactory(factory); DefaultAttributeFactory = factory; } // Helpers private static ConstructorInfo GetAttributeAdapterConstructor(Type attributeType, Type adapterType) { ConstructorInfo constructor = adapterType.GetConstructor(new[] { typeof(ModelMetadata), typeof(ControllerContext), attributeType }); if (constructor == null) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.DataAnnotationsModelValidatorProvider_ConstructorRequirements, adapterType.FullName, typeof(ModelMetadata).FullName, typeof(ControllerContext).FullName, attributeType.FullName ), "adapterType" ); } return constructor; } private static void ValidateAttributeAdapterType(Type adapterType) { if (adapterType == null) { throw new ArgumentNullException("adapterType"); } if (!typeof(ModelValidator).IsAssignableFrom(adapterType)) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.Common_TypeMustDriveFromType, adapterType.FullName, typeof(ModelValidator).FullName ), "adapterType" ); } } private static void ValidateAttributeType(Type attributeType) { if (attributeType == null) { throw new ArgumentNullException("attributeType"); } if (!typeof(ValidationAttribute).IsAssignableFrom(attributeType)) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.Common_TypeMustDriveFromType, attributeType.FullName, typeof(ValidationAttribute).FullName ), "attributeType"); } } private static void ValidateAttributeFactory(DataAnnotationsModelValidationFactory factory) { if (factory == null) { throw new ArgumentNullException("factory"); } } #endregion #region IValidatableObject adapter registration /// <summary> /// Registers an adapter type for the given <see cref="modelType"/>, which must /// implement <see cref="IValidatableObject"/>. The adapter type must derive from /// <see cref="ModelValidator"/> and it must contain a public constructor /// which takes two parameters of types <see cref="ModelMetadata"/> and /// <see cref="ControllerContext"/>. /// </summary> public static void RegisterValidatableObjectAdapter(Type modelType, Type adapterType) { ValidateValidatableModelType(modelType); ValidateValidatableAdapterType(adapterType); ConstructorInfo constructor = GetValidatableAdapterConstructor(adapterType); _adaptersLock.EnterWriteLock(); try { ValidatableFactories[modelType] = (metadata, context) => (ModelValidator)constructor.Invoke(new object[] { metadata, context }); } finally { _adaptersLock.ExitWriteLock(); } } /// <summary> /// Registers an adapter factory for the given <see cref="modelType"/>, which must /// implement <see cref="IValidatableObject"/>. /// </summary> public static void RegisterValidatableObjectAdapterFactory(Type modelType, DataAnnotationsValidatableObjectAdapterFactory factory) { ValidateValidatableModelType(modelType); ValidateValidatableFactory(factory); _adaptersLock.EnterWriteLock(); try { ValidatableFactories[modelType] = factory; } finally { _adaptersLock.ExitWriteLock(); } } /// <summary> /// Registers the default adapter type for objects which implement /// <see cref="IValidatableObject"/>. The adapter type must derive from /// <see cref="ModelValidator"/> and it must contain a public constructor /// which takes two parameters of types <see cref="ModelMetadata"/> and /// <see cref="ControllerContext"/>. /// </summary> public static void RegisterDefaultValidatableObjectAdapter(Type adapterType) { ValidateValidatableAdapterType(adapterType); ConstructorInfo constructor = GetValidatableAdapterConstructor(adapterType); DefaultValidatableFactory = (metadata, context) => (ModelValidator)constructor.Invoke(new object[] { metadata, context }); } /// <summary> /// Registers the default adapter factory for objects which implement /// <see cref="IValidatableObject"/>. /// </summary> public static void RegisterDefaultValidatableObjectAdapterFactory(DataAnnotationsValidatableObjectAdapterFactory factory) { ValidateValidatableFactory(factory); DefaultValidatableFactory = factory; } // Helpers private static ConstructorInfo GetValidatableAdapterConstructor(Type adapterType) { ConstructorInfo constructor = adapterType.GetConstructor(new[] { typeof(ModelMetadata), typeof(ControllerContext) }); if (constructor == null) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.DataAnnotationsModelValidatorProvider_ValidatableConstructorRequirements, adapterType.FullName, typeof(ModelMetadata).FullName, typeof(ControllerContext).FullName ), "adapterType" ); } return constructor; } private static void ValidateValidatableAdapterType(Type adapterType) { if (adapterType == null) { throw new ArgumentNullException("adapterType"); } if (!typeof(ModelValidator).IsAssignableFrom(adapterType)) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.Common_TypeMustDriveFromType, adapterType.FullName, typeof(ModelValidator).FullName ), "adapterType"); } } private static void ValidateValidatableModelType(Type modelType) { if (modelType == null) { throw new ArgumentNullException("modelType"); } if (!typeof(IValidatableObject).IsAssignableFrom(modelType)) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.Common_TypeMustDriveFromType, modelType.FullName, typeof(IValidatableObject).FullName ), "modelType" ); } } private static void ValidateValidatableFactory(DataAnnotationsValidatableObjectAdapterFactory factory) { if (factory == null) { throw new ArgumentNullException("factory"); } } #endregion } }
GetValidators方法的主要实现如下:
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;
我们可以从AttributeFactories和ValidatableFactories里面找到我们需要的factory,DataAnnotationsModelValidationFactory 默认有:
RangeAttribute->RangeAttributeAdapter
RegularExpressionAttribute->RegularExpressionAttributeAdapter
RequiredAttribute->RequiredAttributeAdapter
StringLengthAttribute->StringLengthAttributeAdapter
而这4个adapter都继承与 public class DataAnnotationsModelValidator<TAttribute> : DataAnnotationsModelValidator where TAttribute : ValidationAttribute ,并且都从写了基类的GetClientValidationRules方法。以RangeAttributeAdapter 的为例 ,其实现如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class RangeAttributeAdapter : DataAnnotationsModelValidator<RangeAttribute> { public RangeAttributeAdapter(ModelMetadata metadata, ControllerContext context, RangeAttribute attribute) : base(metadata, context, attribute) { } public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() { string errorMessage = ErrorMessage; // Per Dev10 Bug #923283, need to make sure ErrorMessage is called before Minimum/Maximum return new[] { new ModelClientValidationRangeRule(errorMessage, Attribute.Minimum, Attribute.Maximum) }; } } public class ModelClientValidationRangeRule : ModelClientValidationRule { public ModelClientValidationRangeRule(string errorMessage, object minValue, object maxValue) { ErrorMessage = errorMessage; ValidationType = "range"; ValidationParameters["min"] = minValue; ValidationParameters["max"] = maxValue; } }
这里的AttributeFactories可以通过
public static void RegisterAdapter(Type attributeType, Type adapterType)
public static void RegisterAdapterFactory(Type attributeType, DataAnnotationsModelValidationFactory factory)
这2个方法添加新的成员。而ValidatableFactories默认是没有成员的,可以通过以下方法添加成员
public static void RegisterValidatableObjectAdapter(Type modelType, Type adapterType)
public static void RegisterValidatableObjectAdapterFactory(Type modelType, DataAnnotationsValidatableObjectAdapterFactory factory)
注意以下我们一般自定义的验证类都是继承ValidationAttribute的,所以这里用的就是DefaultAttributeFactory,也就是DataAnnotationsModelValidator,那么就是要调用DataAnnotationsModelValidator的GetClientValidationRules方法。
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() {
IEnumerable<ModelClientValidationRule> results = base.GetClientValidationRules();
IClientValidatable clientValidatable = Attribute as IClientValidatable;
if (clientValidatable != null) {
results = results.Concat(clientValidatable.GetClientValidationRules(Metadata, ControllerContext));
}
return results;
}
这个方法首先调用基类的GetClientValidationRules方法,返回没有元素的一个集合,然后调用我们自定义类的GetClientValidationRules方法,这里也就解释了为什么 我们需要实现IClientValidatable 这个接口了。注意以下DataAnnotationsModelValidator类还有一个 public override IEnumerable<ModelValidationResult> Validate(object container)这个方法,就是我们服务器端的验证,比如会调用我们子类的 public override bool IsValid(object value)方法。
在实际开发中我一般喜欢用ValidationAttribute和IClientValidatable这种方式,当然还可以用Attribute-》ModelValidator-》AssociatedValidatorProvider 比如MVC中的扩展点(九)验证 最后在调用ModelValidatorProviders.Providers.Add(new ConfirmValidatorProvider());来添加ValidatorProvider。
ModelValidatorProviders主要关心2个地方,1是获取ModelMetadata;
var metadataProvider = new DataAnnotationsModelMetadataProvider(); var metadata = metadataProvider.GetMetadataForType(null, section.SectionDataType); foreach (ModelMetadata meta in metadata.Properties) { var validatorList = meta.GetValidators(this.ControllerContext).ToList(); if (validatorList.Count > 0) { foreach (var validator in validatorList) { var rules = validator.GetClientValidationRules(); foreach (var rule in rules) { object value; switch (rule.ValidationType) { case "required": XXXXXXX break; } } } } }
2是如何调用GetClientValidationRules方法。
ModelMetadata是一个很复杂的东西,所以本文没有提及,大家可以参考:
客户端的验证就相对简单了很多,我们先说这一段code:
jQuery.validator.unobtrusive.adapters.add('mulregular', ['regexs', 'minmatchno'], function (options) {
options.rules["mulregular"] = {
regexs: options.params.regexs,
minmatchno: options.params.minmatchno
};
if (options.message) {
options.messages['mulregular'] = options.message;
}
});
其中的Add的实现如下:
adapters.add = function (adapterName, params, fn) {
if (!fn) { // Called with no params, just a function
fn = params;
params = [];
}
this.push({ name: adapterName, params: params, adapt: fn });
return this;
};
而我们定义的这个方法在parseElement方法中调用:
$.each(this.adapters, function () {
var prefix = "data-val-" + this.name, //获取验证前缀 如data-val-mulregular
message = $element.attr(prefix),// 获取error message
paramValues = {};
if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy)
prefix += "-";
$.each(this.params, function () { //params就是传递的['regexs', 'minmatchno']
paramValues[this] = $element.attr(prefix + this);// 获取参数值 如data-val-mulregular-minmatchno 这里的this就是minmatchno
});
this.adapt({ //调用我们在jQuery.validator.unobtrusive.adapters.add定义的方法
element: element,
form: form,
message: message,
params: paramValues,
rules: rules,
messages: messages
});
}
再来看客户端验证的实现code:
jQuery.validator.addMethod('mulregular', function (value, element, param) {
if (this.optional(element)) {
return true;
}
var regs = param["regexs"].split(",");
var minmatchno = param["minmatchno"] - 0;
for (var i = 0; i < regs.length; i++) {
var match = new RegExp(regs[i]).exec(value);
if (match && (match.index === 0) && (match[0].length === value.length)) {
minmatchno -= 1;
}
}
return minmatchno <= 0;
});
其中addMethod的实现很简单
addMethod: function( name, method, message ) {
$.validator.methods[name] = method;
$.validator.messages[name] = message !== undefined ? message : $.validator.messages[name];
if ( method.length < 3 ) {
$.validator.addClassRules(name, $.validator.normalizeRule(name));
}
}
这里定义的方法在check里面调用:
大家看到的这里的parameter的数据和我们在unobtrusive.adapters中返回的参数一致,但是怎么获取的我也不是很明白。里面涉及到的内容很多很多。
网上关于这一方面的还很多
ASP.NET MVC Unobtrusive JavaScript 实现 onfocusout 验证, onfocusin 清除错误
ASP.NET MVC的客户端验证:jQuery验证在Model验证中的实现
改写jquery.validate.unobtrusive.js实现气泡提示mvc错误
Query validate 根据 asp.net MVC的验证提取简单快捷的验证方式(jquery.validate.unobtrusive.js)