您的位置:首页 > 编程语言 > ASP

asp.net mvc源码分析-ModelValidatorProviders 客户端的验证

2016-04-02 13:55 831 查看
几年写过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,所以我们来看看

/* ****************************************************************************
*
* 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;

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

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如何实现自定义验证(服务端验证+客户端验证)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: