Pro ASP.NET MVC 3 Framework 读书笔记之数据校验

MVC3.0中提供了丰富的Model数据校验,这对于数据开发是非常方便,校验按实现方式来分,有如下几类:

  1. 在controller中进行校验
  2. 在Model上的属性的元数据上面加入验证逻辑
  3. ModelBinder上进行校验,并可以自定义属性进行验证
  4. Model自校验
  5. 自定义ValidateProvider进行校验
  6. 手动进行数据校验
  7. 远程校验(Ajax进行校验)

 

 

从客户端和服务端来分:

1,2,3,4,5,7属于服务端校验;

2,6属于客户端校验

 

1、在Controller进行校验

这是一种最直接的实现方式

示例代码如下:

   1: [HttpPost]
   2: public ViewResult MakeBooking(Appointment appt) {
   3:     if (string.IsNullOrEmpty(appt.ClientName)) {
   4:         ModelState.AddModelError("ClientName", "Please enter your name");
   5:     }
   6:     if (ModelState.IsValidField("Date") && DateTime.Now > appt.Date) {
   7:         ModelState.AddModelError("Date", "Please enter a date in the future");
   8:     }
   9:     if (!appt.TermsAccepted) {
  10:         ModelState.AddModelError("TermsAccepted", "You must accept the terms");
  11:     }
  12:     if (ModelState.IsValid) {
  13:         repository.SaveAppointment(appt);
  14:         return View("Completed", appt);
  15:     } else {
  16:         return View();
  17:     }
  18: }

 

通过上面的代码把出错信息设置Controller中ModelState中,这样就可以通过判断ModelState.IsValid进行验证Model是否通过验证,上面的代码演示,如果通过验证,就返回保存成功的视图,如果失败就刷新当前页面,并显示错误信息.

2、在Model上的属性的元数据上面加入验证逻辑

在属性的元数据上面加入验证逻辑,这是一种使用比较多方式,在最早的Winform也很多的使用这种方式进行校验,这种方式不是MVC特有的方式,同时还提供了对客户端校验的支持(这种支持不知道是不是在MVC中加入的),很好的实现客户端和服务端同时进行校验的问题。

这种实现的示例如下所示:

   1: public class Appointment {
   2:        [Required]
   3:        public string ClientName { get; set; }
   4:        [DataType(DataType.Date)]
   5:        [Required(ErrorMessage="Please enter a date")]
   6:        public DateTime Date { get; set; }
   7:        [Range(typeof(bool), "true", "true", ErrorMessage="You must accept the terms")]
   8:        public bool TermsAccepted { get; set; }
   9:    }

 

系统定义的属性有:

属性 示例 备注
Compare [Conmpre(“其他属性”)]  
Range [Range(10,20)]  
RegularExpression [RegularExpression(“pattern”)]  
Required [Required]  
StringLength [StringLength(10)]  

 

同时,我们还可以自定义校验属性,并加到Model属性或其自身上,示例属性如下:

   1: public class MustBeTrueAttribute : ValidationAttribute {
   2:     public override bool IsValid(object value) {
   3:         return value is bool && (bool)value;
   4:     }
   5: }
   6:  
   7: public class FutureDateAttribute : RequiredAttribute {
   8:     public override bool IsValid(object value) {
   9:         return base.IsValid(value) && 
  10:             value is DateTime &&  
  11:             ((DateTime)value) > DateTime.Now;
  12:     }
  13: }
  14: public class AppointmentValidatorAttribute : ValidationAttribute {
  15:     public AppointmentValidatorAttribute() {
  16:         ErrorMessage = "Joe cannot book appointments on Mondays";
  17:     }
  18: public override bool IsValid(object value) {
  19:        Appointment app = value as Appointment;
  20:        if (app == null || string.IsNullOrEmpty(app.ClientName) || app.Date == null) {
  21:            // we don't have a model of the right type to validate, or we don't have
  22:            // the values for the ClientName and Date properties we require
  23:            return true;
  24:        } else {
  25:            return !(app.ClientName == "Joe" && app.Date.DayOfWeek == DayOfWeek.Monday);
  26:        } 
  27:    }

如果自定义属性实现了接口:

image

就会自动实现客户端校验。

3、ModelBinder上进行校验,并可以自定义属性进行验证

DefaultModelBinder实现了基本数据校验功能,在客户端提交数据到服务端后,默认ModelBinder会自动从表单中解析出相应属性并给相应的Model属性赋值,同时会自动调用属性的校验设置,如果要统一实现数据校验,可以通过继承“DefaultModelBinder”来实现自定义的通用校验,我们只需要重写此类的两个方法,如下表所示:

方法 描述 默认实现
OnModelUpdate 当ModelBinder要试着把所有属性赋值给实体时,会自动调用。 通过自动调用元数据设置进行校验,并把校验错误写入到ModelState中。
SetProperty 当ModelBinder要给指定属性进行赋值时,就就会自动调用此方法 。 如果要设置的变量值为null或没有变量进行赋值,就会在ModelState中注册“The <name> field is required”错误信息;如果变量值不能被解析,就会在ModelState中注册出错信息“The value <value> is not valid for <name>”

 

示例如下:

   1: public class ValidatingModelBinder : DefaultModelBinder {
   2:        protected override void SetProperty(ControllerContext controllerContext, 
   3:            ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, 
   4:            object value) {
   5:            // make sure we call the base implementation
   6:            base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
   7:            // perform our property-level validation
   8:            switch (propertyDescriptor.Name) {
   9:                case "ClientName":
  10:                    if (string.IsNullOrEmpty((string)value)) {
  11:                        bindingContext.ModelState.AddModelError("ClientName", 
  12:                            "Please enter your name");
  13:                    }
  14:                    break;
  15:                case "Date":
  16:                    if (bindingContext.ModelState.IsValidField("Date") && 
  17:                        DateTime.Now > ((DateTime)value)) {
  18:                        bindingContext.ModelState.AddModelError("Date", 
  19:                            "Please enter a date in the future");
  20:                    }
  21:                    break;
  22:                case "TermsAccepted":
  23:                    if (!((bool)value)) {
  24:                        bindingContext.ModelState.AddModelError("TermsAccepted", 
  25:                            "You must accept the terms");
  26:                    }
  27:                    break;
  28:            }
  29:        }
  30:        protected override void OnModelUpdated(ControllerContext controllerContext,
  31:            ModelBindingContext bindingContext) {
  32:            // make sure we call the base implementation
  33:            base.OnModelUpdated(controllerContext, bindingContext);
  34:            // get the model
  35:            Appointment model = bindingContext.Model as Appointment;
  36: // apply our model-level validation
  37:             if (model != null &&
  38:                 bindingContext.ModelState.IsValidField("ClientName") &&
  39:                 bindingContext.ModelState.IsValidField("Date") &&
  40:                 model.ClientName == "Joe" &&
  41:                 model.Date.DayOfWeek == DayOfWeek.Monday) {
  42:                 bindingContext.ModelState.AddModelError("",
  43:                     "Joe cannot book appointments on Mondays");
  44:             }
  45:         }
  46:     }
  47: }

 

 

自定义的ModelBinder安装方法如下:

   1: protected void Application_Start() {
   2:     AreaRegistration.RegisterAllAreas();
   3:     ModelBinders.Binders.Add(typeof(Appointment), new ValidatingModelBinder());
   4:     RegisterGlobalFilters(GlobalFilters.Filters);
   5:     RegisterRoutes(RouteTable.Routes);
   6: }

 

注册方法,是对指定的类型进行设置指定的ModelBinder.

4、在Model上面实现校验接口

实现自校验的Model,就要Model实现接口:image这个接口只有一个方法,

image

当ModelBinder赋值给Model每个属性时,会自动调用这个接口进行数据校验

示例代码如下:

   1: public class Appointment : IValidatableObject {
   2:     public string ClientName { get; set; }
   3:     [DataType(DataType.Date)]
   4:     public DateTime Date { get; set; }
   5:     public bool TermsAccepted { get; set; }
   6:     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
   7:         List<ValidationResult> errors = new List<ValidationResult>();
   8:         if (string.IsNullOrEmpty(ClientName)) {
   9:             errors.Add(new ValidationResult("Please enter your name"));
  10:        }
  11:         if (DateTime.Now > Date) {
  12:             errors.Add(new ValidationResult("Please enter a date in the future"));
  13:         }
  14:         if (errors.Count == 0 && ClientName == "Joe" 
  15:             && Date.DayOfWeek == DayOfWeek.Monday) {
  16:             errors.Add(new ValidationResult("Joe cannot book appointments on Mondays"));
  17:         }
  18:         if (!TermsAccepted) {
  19:             errors.Add(new ValidationResult("You must accept the terms"));
  20:         }
  21:         return errors;
  22:     }
  23: }

 

5、自定义ValidateProvider进行校验

自定义ValidateProvider,通常都是从“ ModelValidationProvider ”继承并重载方法“GetValidators”来实现,接口签名如下:

   1: IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)

这个方法由Model的每个属性调用一次,再Model自身调用一次,如果不对Model进行校验,可以返回“Enumerable.Empty<ModelValidator>()”

接口参数“ModelMetadata metadata”关键属性说明如下:

属性 描述
ContainerType 属性类型
PropertyName 属性名称
ModelType Model类型

 

示例代码如下:

   1: public class CustomValidationProvider : ModelValidatorProvider {
   2:     public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata,
   3:         ControllerContext context) {
   4: if (metadata.ContainerType == typeof(Appointment)) {
   5:            return new ModelValidator[] {
   6:            new AppointmentPropertyValidator(metadata, context)
   7:        };
   8:        } else if (metadata.ModelType == typeof(Appointment)) {
   9:            return new ModelValidator[] { 
  10:            new AppointmentValidator(metadata, context) 
  11:        };
  12:        }
  13:        return Enumerable.Empty<ModelValidator>();
  14:    }
  15:  
  16: public class AppointmentPropertyValidator : ModelValidator {
  17:     public AppointmentPropertyValidator(ModelMetadata metadata, ControllerContext context)
  18:         : base(metadata, context) {
  19:     }
  20:     public override IEnumerable<ModelValidationResult> Validate(object container) {
  21:         Appointment appt = container as Appointment;
  22:         if (appt != null) {
  23:             switch (Metadata.PropertyName) {
  24:                 case "ClientName":
  25:                     if (string.IsNullOrEmpty(appt.ClientName)) {
  26:                         return new ModelValidationResult[] {
  27:                         new ModelValidationResult {
  28:                             MemberName = "ClientName",
  29:                             Message = "Please enter your name"
  30:                         }};
  31:                     }
  32:                     break;
  33:                 case "Date":
  34:                     if (appt.Date == null || DateTime.Now > appt.Date) {
  35:                         return new ModelValidationResult[] {
  36:                         new ModelValidationResult {
  37:                         MemberName = "Date",
  38:                         Message = "Please enter a date in the future"
  39:                     }};
  40:                     }
  41: break;
  42:                case "TermsAccepted":
  43:                    if (!appt.TermsAccepted) {
  44:                        return new ModelValidationResult[] {
  45:                        new ModelValidationResult {
  46:                            MemberName = "TermsAccepted",
  47:                            Message = "You must accept the terms"
  48:                        }};
  49:                    }
  50:                    break;
  51:            }
  52:        }
  53:        return Enumerable.Empty<ModelValidationResult>();
  54:    }
  55: public class AppointmentValidator : ModelValidatorCustomValidator<Appointment> {
  56:     public AppointmentValidator(ModelMetadata metadata, ControllerContext context)
  57:         : base(metadata, context) {
  58:     }
  59:     public override void Validate(Appointment container, 
  60:         IList<ModelValidationResult> errors) {
  61:         Appointment appt = (Appointment)Metadata.Model;
  62:         if (appt.ClientName == "Joe" && appt.Date.DayOfWeek == DayOfWeek.Monday) {
  63:             errors.Add(new ModelValidationResult {
  64:                 MemberName = "",
  65:                 Message = "Joe cannot book appointments on Mondays"
  66:             });
  67:         }
  68:     }
  69: }

 

注册方法:

   1: protected void Application_Start() {
   2:     AreaRegistration.RegisterAllAreas();
   3:     ModelValidatorProviders.Providers.Add(new CustomValidationProvider());
   4:     RegisterGlobalFilters(GlobalFilters.Filters);
   5:     RegisterRoutes(RouteTable.Routes);
   6: }

 

 

7、远程校验

远程校验通过Ajax调用Controller的Action来实现

样例Action如下:

   1: public JsonResult ValidateDate(string Date) {
   2:         DateTime parsedDate;
   3:         if (!DateTime.TryParse(Date, out parsedDate)) {
   4:             return Json("Please enter a valid date (mm/dd/yyyy)",
   5:                 JsonRequestBehavior.AllowGet);
   6:         } else if (DateTime.Now > parsedDate) {
   7:             return Json("Please enter a date in the future", JsonRequestBehavior.AllowGet);
   8:         } else {
   9:             return Json(true, JsonRequestBehavior.AllowGet);
  10:         }
  11:     }

 

 

注:所有代码都来源于(仅仅作样例):

image

posted @ 2011-07-14 21:27  cdboy  阅读(887)  评论(0编辑  收藏  举报