Pro ASP.NET MVC 3 Framework 读书笔记之数据校验
MVC3.0中提供了丰富的Model数据校验,这对于数据开发是非常方便,校验按实现方式来分,有如下几类:
- 在controller中进行校验
- 在Model上的属性的元数据上面加入验证逻辑
- ModelBinder上进行校验,并可以自定义属性进行验证
- Model自校验
- 自定义ValidateProvider进行校验
- 手动进行数据校验
- 远程校验(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 have22: // the values for the ClientName and Date properties we require23: return true;24: } else {25: return !(app.ClientName == "Joe" && app.Date.DayOfWeek == DayOfWeek.Monday);26: }
27: }
如果自定义属性实现了接口:
就会自动实现客户端校验。
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实现接口:这个接口只有一个方法,
当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: }
注:所有代码都来源于(仅仅作样例):