• 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,所以我们来看看

    /* ****************************************************************************
     *
     * 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
        }
    }
    View Code

    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;
            }
        }
    View Code

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

    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如何实现自定义验证(服务端验证+客户端验证)

  • 相关阅读:
    怎么用js实现jq的removeClass方法
    减少事件绑定次数
    JS setAttribute兼容
    css3常用动画+动画库
    小tip: transition与visibility
    image的srcset属性
    jqeury点击空白关闭弹窗
    卡片翻转效果
    div+css 圆角加阴影
    函数
  • 原文地址:https://www.cnblogs.com/majiang/p/5346935.html
Copyright © 2020-2023  润新知