几年写过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 > ) {
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,所以我们来看看

/* ****************************************************************************
*
* 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 的为例 ,其实现如下:

  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 > )
{
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是一个很复杂的东西,所以本文没有提及,大家可以参考:

ASP.NET MVC Model元数据(一)

ASP.NET MVC Model元数据(二)

ASP.NET MVC Model元数据(三)

ASP.NET MVC Model元数据(四)

ASP.NET MVC Model元数据(五)

客户端的验证就相对简单了很多,我们先说这一段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)

ASP.NET MVC如何实现自定义验证(服务端验证+客户端验证)

asp.net mvc源码分析-ModelValidatorProviders 客户端的验证的更多相关文章

  1. asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证

    原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 在前面的文章中我们曾经涉及到ControllerActionInvoker类GetPara ...

  2. ASP.NET MVC 源码分析(一)

    ASP.NET MVC 源码分析(一) 直接上图: 我们先来看Core的设计: 从项目结构来看,asp.net.mvc.core有以下目录: ActionConstraints:action限制相关 ...

  3. ASP.NET MVC源码分析

    MVC4 源码分析(Visual studio 2012/2013) HttpModule中重要的UrlRoutingModule 9:this.OnApplicationPostResolveReq ...

  4. asp.net MVC 源码分析

    先上一张图吧 asp.net请求机制的图  by传智播客邹华栋老师 然后是 邹老师添加MVC请求过程的图 其实MVC 是在.netframework上加了一个过滤器  HttpModule 在C:\W ...

  5. asp.net mvc源码分析-Route的GetRouteData

    我知道Route这里东西应该算路由,这里把它放到mvc里面有些不怎么合适,但是我想大家多数遇到路由都是在mvc的时候吧.首先我们还是来看看GetRouteData方法吧 [csharp] public ...

  6. asp.net mvc源码分析-Action篇 IModelBinder

    我们首先还是看看ReflectedParameterBindingInfo的Binder属性吧: public override IModelBinder Binder {            ge ...

  7. ASP.NET MVC源码分析系列

    Controller下的JsonResult的ExecuteResult方法 public override void ExecuteResult(ControllerContext context) ...

  8. ASP.NET MVC 源码分析(二) —— 从 IRouteBuilder认识路由构建

    我们来看IRouteBuilder的定义: public interface IRouteBuilder { IRouter DefaultHandler { get; set; } IService ...

  9. ASP.NET WebForm / MVC 源码分析

    浏览器 Url:https//localhost:6565/Home/Index ,https//localhost:6565/WebForm1.aspx,请求服务器(构建请求报文,并且将请求报文发送 ...

随机推荐

  1. 大数据技术之_14_Oozie学习_Oozie 的简介+Oozie 的功能模块介绍+Oozie 的部署+Oozie 的使用案列

    第1章 Oozie 的简介第2章 Oozie 的功能模块介绍2.1 模块2.2 常用节点第3章 Oozie 的部署3.1 部署 Hadoop(CDH版本的)3.1.1 解压缩 CDH 版本的 hado ...

  2. 代理设置。 安卓工作室配置用http代理。gradle可能需要这些http代理设置去访问互联网。例如下载依赖。 你想要复制ide的代理配置到这个项目的gradle属性文件吗?

    代理设置. 安卓工作室配置用http代理.gradle可能需要这些http代理设置去访问互联网.例如下载依赖. 你想要复制ide的代理配置到这个项目的gradle属性文件吗? 查看更多细节,请参阅开发 ...

  3. 【NOI2005】聪聪和可可 概率与期望 记忆化搜索

    1415: [Noi2005]聪聪和可可 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1635  Solved: 958[Submit][Statu ...

  4. mybatis学习笔记(三)-- 优化数据库连接配置

    原来直接把数据库连接配置信息写在conf.xml配置中,如下 <?xml version="1.0" encoding="UTF-8"?> < ...

  5. 并发编程(二):全视角解析volatile

    一.目录 1.引入话题-发散思考 2.volatile深度解析 3.解决volatile原子性问题 4.volatile应用场景 二.引入话题-发散思考 public class T1 { /*vol ...

  6. HDU 4731 Minimum palindrome (2013成都网络赛,找规律构造)

    Minimum palindrome Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Other ...

  7. SPI SWD Protocol Implement

    //================================================================================= // ARM SWD Mode ...

  8. JTAG - Debug Cable Driver/Receiver

  9. 智能指针scoped_ptr

    对应C++11中的unique_ptr #include <iostream> #include <memory> class foo { public: foo() { st ...

  10. 如何修改Mac截屏保存路径

    MAC OS X系统默认的截图路径是桌面文件夹,默认的截图格式是 PNG 图片格式,如何自定义设置呢? 截图保存路径 打开终端(Terminal)并输入如下命令: defaults write com ...