Asp.net MVC源码分析--Model Validation(Server端)实现(1)

来源:岁月联盟 编辑:exp 时间:2011-12-31

一.MVC Validation 用法:
在Asp.net MVC 框架中如果需要对Model 对象加入验证,我们可以在Model的属性上标记所有继承于ValidationAttribute的Attribute特性.
例如下面的代码中,StringLength/Range/Compare 都是继承于ValidationAttribute类.
 public class LogOnModel
    {
        [Required]     
        [StringLength(10)]
        public string UserName { get; set; }

        [Required]
        [Range(5,10)]
        public string Password { get; set; }

       [Compare("NewPassword", ErrorMessage = "The new      password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }

        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
复制代码
在Action 中我们可以调用 ValidateModel 方法对Model对象进行验证,如果验证没有通过则会抛出InvalidOperationException异常,同时ModelState.IsValid状态为false
   [HttpPost]
        public ActionResult LogOn(LogOnModel model, string returnUrl)
        {         
            this.ValidateModel(model);
            if (ModelState.IsValid)
            {
                if (MembershipService.ValidateUser(model.UserName, model.Password))
                {
                    FormsService.SignIn(model.UserName, model.RememberMe);
                    if (Url.IsLocalUrl(returnUrl))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index", "Home");
                    }
                }
                else
                {
                    ModelState.AddModelError("", "The user name or password provided is incorrect.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }
复制代码
这里需要注意的是如果我们不调用ValidateModel 方法Action上的Model对象默认也是能够验证的,这是因为在绑定对象阶段DefaultModelBinder.BindComplexElementalModel方法中MVC触发了验证的方法。
DefaultModelBinder.cs
 internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) {
            // need to replace the property filter + model object and create an inner binding context
            ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);

            // validation
            if (OnModelUpdating(controllerContext, newBindingContext)) {
                BindProperties(controllerContext, newBindingContext);
                OnModelUpdated(controllerContext, newBindingContext);
            }
        }
 protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {
            Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

            foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null)) {
                string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName);

                if (!startedValid.ContainsKey(subPropertyName)) {
                    startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName);
                }

                if (startedValid[subPropertyName]) {
                    bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message);
                }
            }
        }
复制代码
二.ValidateModel方法源码分析
下面我们看一下MVC 是如何实现ValidateModel方法验证的.
 1  protected internal void ValidateModel(object model) {
 2             ValidateModel(model, null /* prefix */);
 3         }
 4
 5         protected internal void ValidateModel(object model, string prefix) {
 6             if (!TryValidateModel(model, prefix)) {
 7                 throw new InvalidOperationException(
 8                     String.Format(
 9                         CultureInfo.CurrentCulture,
10                         MvcResources.Controller_Validate_ValidationFailed,
11                         model.GetType().FullName
12                     )
13                 );
14             }
15         }
16
17  protected internal bool TryValidateModel(object model) {
18             return TryValidateModel(model, null /* prefix */);
19         }
20
21         protected internal bool TryValidateModel(object model, string prefix) {
22             if (model == null) {
23                 throw new ArgumentNullException("model");
24             }
25
26             ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
27
28             foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(metadata, ControllerContext).Validate(null)) {
29                 ModelState.AddModelError(DefaultModelBinder.CreateSubPropertyName(prefix, validationResult.MemberName), validationResult.Message);
30             }
31
32             return ModelState.IsValid;
33         }
复制代码
我们可以看到第28行MVC是调用了ModelValidator.GetModelValidator 来获取Model 的Validator 对象的.我们看一下这个方法的实现,它返回的是CompositeModelValidator对象.
   public static ModelValidator GetModelValidator(ModelMetadata metadata, ControllerContext context) {
            return new CompositeModelValidator(metadata, context);
        }
复制代码
CompositeModelValidator.cs
 1  private class CompositeModelValidator : ModelValidator {
 2             public CompositeModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
 3                 : base(metadata, controllerContext) {
 4             }
 5
 6             public override IEnumerable<ModelValidationResult> Validate(object container) {
 7                 bool propertiesValid = true;
 8
 9                 foreach (ModelMetadata propertyMetadata in Metadata.Properties) {
10                     foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext)) {
11                         //Robbin:一但Validate返回了ModelValidationResult就证明验证没有通过.
12                         foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model)) {
13                             propertiesValid = false;
14                             yield return new ModelValidationResult {
15                                 MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName),
16                                 Message = propertyResult.Message
17                             };
18                         }
19                     }
20                 }
21
22                 if (propertiesValid) {
23                     foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext)) {
24                         foreach (ModelValidationResult typeResult in typeValidator.Validate(container)) {
25                             yield return typeResult;
26                         }
27                     }
28                 }
29             }
30         }
复制代码
我们再看一下上面第10行,propertyMetadata.GetValidators这个方法的实现,他返回了所有的Validator的实现。并且在12行,调用这些实现的Validate方法进行验证操作.
public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context) {
            return ModelValidatorProviders.Providers.GetValidators(this, context);
        }
复制代码
它调用了ModelValidatorProviders.Providers对象的GetValidators 方法. 这个对象的类型是ModelValidatorProviderCollection接下来我们看一下ModelValidatorProviders.Providers的实现
  public static class ModelValidatorProviders {

        private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection() {
            new DataAnnotationsModelValidatorProvider(),
            new DataErrorInfoModelValidatorProvider(),
            new ClientDataTypeModelValidatorProvider()
        };

        public static ModelValidatorProviderCollection Providers {
            get {
                return _providers;
            }
        }

    }
复制代码
这个对象初始化时默认包括了三个ValidatorProvider,在这里我们需要着重注意的是DataAnnotationsModelValidatorProvider,它提供了所有系统默认的Validation规则(除了RemoteAttribute).
下一篇我们来详细的分析一下DataAnnotationsModelValidatorProvider的实现以及使用的设计模式.

 摘自 十一月的雨