asp.net mvc源码分析ModelValidatorProviders

在上篇文章asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证最后提到了ModelValidatorProviders ,这里我们以DataAnnotationsModelValidatorProvider来说说整过的验证过程。因为 DataAnnotationsModelValidatorProvider这个是我们平时用的最多的情况。其GetValidators的具体实现如 下:

  protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) {
            _adaptersLock.EnterReadLock();

            try {
                List<ModelValidator> results = new List<ModelValidator>();

                // Add an implied [Required] attribute for any non-nullable value type,
                // unless they've configured us not to do that.
                if (AddImplicitRequiredAttributeForValueTypes &&
                        metadata.IsRequired &&
                        !attributes.Any(a => a is RequiredAttribute)) {
                    attributes = attributes.Concat(new[] { new RequiredAttribute() });
                }

                // Produce a validator for each validation attribute we find
                foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>()) {
                    DataAnnotationsModelValidationFactory factory;
                    if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory)) {
                        factory = DefaultAttributeFactory;
                    }
                    results.Add(factory(metadata, context, attribute));
                }

                // Produce a validator if the type supports IValidatableObject
                if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType)) {
                    DataAnnotationsValidatableObjectAdapterFactory factory;
                    if (!ValidatableFactories.TryGetValue(metadata.ModelType, out factory)) {
                        factory = DefaultValidatableFactory;
                    }
                    results.Add(factory(metadata, context));
                }

                return results;
            }
            finally {
                _adaptersLock.ExitReadLock();
            }
        }

 首先从这里的特性取出是ValidationAttribute特性的attribute,然后根据attribute的type取出相应的DataAnnotationsModelValidationFactory,其中以下4个特性和其DataAnnotationsModelValidationFactory返回的ModelValidator实例一一对应:
RangeAttribute->RangeAttributeAdapter
RegularExpressionAttribute->RegularExpressionAttributeAdapter
RequiredAttribute->RequiredAttributeAdapter
StringLengthAttribute->StringLengthAttributeAdapter

这里的4个AttributeAdapter都继承于 DataAnnotationsModelValidator<TAttribute>   -> DataAnnotationsModelValidator ->ModelValidator.

如果没有取消相应的DataAnnotationsModelValidationFactory就调用默认的 DefaultValidatableFactory,它返回一个ValidatableObjectAdapter的一个实例;然后依次调用它们的 Validate方法。以上面4个attribute为例,它们的Validate方法默认的实现在 DataAnnotationsModelValidator中的Validate方法:


        public override IEnumerable<ModelValidationResult> Validate(object container) {
            // Per the WCF RIA Services team, instance can never be null (if you have
            // no parent, you pass yourself for the "instance" parameter).
            ValidationContext context = new ValidationContext(container ?? Metadata.Model, null, null);
            context.DisplayName = Metadata.GetDisplayName();

            ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context);
            if (result != ValidationResult.Success) {
                yield return new ModelValidationResult {
                    Message = result.ErrorMessage
                };
            }
        }

调用每个Attribute的GetValidationResult方法。验证失败就会返 回一个ModelValidationResult实例。到这里我们知道DataAnnotationsModelValidator的Validate 方法是怎么调用delete,但是这里类里面还有一个GetClientValidationRules方法,它好像是一个客户端的验证啊。调用 attribute的GetClientValidationRules方法。该方法是在哪里调用的了。我们有时候在view里面有这样的方 法 @Html.ValidationMessageFor(model => model.UserName)。

ValidationMessage方法中有这么一段

  if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled) {
                    builder.MergeAttribute("data-valmsg-for", modelName);
                    builder.MergeAttribute("data-valmsg-replace", replaceValidationMessageContents.ToString().ToLowerInvariant());
                }
                else {
                    FieldValidationMetadata fieldMetadata = ApplyFieldValidationMetadata(htmlHelper, modelMetadata, modelName);
                    // rules will already have been written to the metadata object
                    fieldMetadata.ReplaceValidationMessageContents = replaceValidationMessageContents; // only replace contents if no explicit message was specified

                    // client validation always requires an ID
                    builder.GenerateId(modelName + "_validationMessage");
                    fieldMetadata.ValidationMessageId = builder.Attributes["id"];
                }

最终会调用一个ApplyFieldValidationMetadata方法,不过默认的UnobtrusiveJavaScriptEnabled为true。

   private static FieldValidationMetadata ApplyFieldValidationMetadata(HtmlHelper htmlHelper, ModelMetadata modelMetadata, string modelName) {
            FormContext formContext = htmlHelper.ViewContext.FormContext;
            FieldValidationMetadata fieldMetadata = formContext.GetValidationMetadataForField(modelName, true /* createIfNotFound */);

            // write rules to context object
            IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(modelMetadata, htmlHelper.ViewContext);
            foreach (ModelClientValidationRule rule in validators.SelectMany(v => v.GetClientValidationRules())) {
                fieldMetadata.ValidationRules.Add(rule);
            }


            return fieldMetadata;
        }
这里会依次调用我们ModelValidator的GetClientValidationRules方法。
例如我们的代码调用如下:

如果UnobtrusiveJavaScriptEnabled为true下的html如下:

UnobtrusiveJavaScriptEnabled为false的html如下:

我们以UnobtrusiveJavaScriptEnabled为true来说说客服端验证。在生成的html中data-val="true"表示要启用客户端验证,data-val-表示我们要验证的属性。负责验证的js是jquery.validate.unobtrusive.js,里面有这么一句:

 $(selector).find(":input[data-val=true]").each(function () {
                $jQval.unobtrusive.parseElement(this, true);
            });

具体是怎么验证的我这里就忽略了.

 

原文地址:https://www.cnblogs.com/majiang/p/2770174.html