Model Validation in Asp.net MVC
原文:Model Validation in Asp.net MVC
本文用于记录Pro ASP.NET MVC 3 Framework中阐述的数据验证的方式。
先说服务器端的吧。最简单的一种方式自然是直接在Action方法中来进行了,如下:
[HttpPost] public ViewResult MakeBooking(Appointment appt) { if (String.IsNullOrWhiteSpace(appt.ClientName)) { ModelState.AddModelError("ClientName", "Please enter your name"); } if (ModelState.IsValidField("Date") && DateTime.Now > appt.Date) { ModelState.AddModelError("Date", "Please enter a date in the future"); } if (!appt.TermsAccepted) { ModelState.AddModelError("TermsAccepted", "You must accept the terms"); } if (ModelState.IsValidField("ClientName") && ModelState.IsValidField("Date") && appt.ClientName == "Joe" && appt.Date.DayOfWeek == DayOfWeek.Monday) { ModelState.AddModelError("", "Joe cannot book appointments on Mondays"); } if (ModelState.IsValid) { repository.SaveAppointment(appt); return View("Completed", appt); } else { return View(); } }
补充Appointment类源码如下:
public class Appointment { public string ClientName { get; set; } [DataType(DataType.Date)] public DateTime Date { get; set; } public bool TermsAccepted { get; set; } }
可以看到,Appointment类很POCO,其中Date属性上的DataType属性,不过是标注Date属性值为DateTime的Date部分(去掉Time部分)。再看action内部,将传入的appointment对象属性进行了一个遍历校验。最后,ModelState.AddModelError("", "Joe cannot book appointments on Mondays");
是标注一个对象模型级别的错误(方法的key参数为空),模型级别错误可以标注多个,它们均将通过@Html.ValidationSummary()显示错误信息。
上述action对应的view为:
@model PageValidation.Models.Appointment @{ ViewBag.Title = "Make A Booking";}<h4>Book an Appointment</h4>@using (Html.BeginForm()){ @Html.ValidationSummary(); <p> Your name: @Html.EditorFor(m => m.ClientName) @Html.ValidationMessageFor(m => m.ClientName) </p> <p> Appointment Date: @Html.EditorFor(m => m.Date) @Html.ValidationMessageFor(m => m.Date) </p> <p> @Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions @Html.ValidationMessageFor(m => m.TermsAccepted) </p> <input type="submit" value="Make Booking" /> }
这个时候,运行程序,神马都不填写然后提交时,页面提示如下:

如果不想form中错误提示重复(顶部的summary和顶部的detail),将@Html.ValidationSummary();
更新为@Html.ValidationSummary(true); 即可。这个时候,顶部Validation Summary部分只会提示model-level错误了,比如上文中的ModelState.AddModelError("", "Joe cannot book appointments on Mondays");。
关于@Html.ValidationSummary()更多细节,请MSDN。
另外,还有一个view的问题是,Firefox和Chrome等一些浏览器上,对checkbox样式的设置不取作用,上图中的效果是通过在checkbox外层包一个div,将checkbox样式转移到div上来实现的。具体为:在项目Views\Shared\EditorTemplates目录下,建立一个Boolean.cshtml文件以覆盖asp.net mvc默认的行为。文件内容如下:
@model bool? @if (ViewData.ModelMetadata.IsNullableValueType){ @Html.DropDownListFor(m => m, new SelectList(new[] { "Not Set", "True", "False" }, Model));}else{ ModelState state = ViewData.ModelState[ViewData.ModelMetadata.PropertyName]; bool value = Model ?? false; if (state != null && state.Errors.Count > ) { <div class="input-validation-error" style="float: left"> @Html.CheckBox("", value) </div> } else { @Html.CheckBox("", value) }}
服务器端验证第2种方式是通过Model Binder了。我们继承DefaultModelBinder来写一个Appointment需要的类:
public class ValidatingModelBinder : DefaultModelBinder { protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) { // make sure we call the base implementation base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value); // perform our property-level validation switch (propertyDescriptor.Name) { case "ClientName": if (string.IsNullOrEmpty((string)value)) { bindingContext.ModelState.AddModelError("ClientName", "Please enter your name"); } break; case "Date": if (bindingContext.ModelState.IsValidField("Date") && DateTime.Now > ((DateTime)value)) { bindingContext.ModelState.AddModelError("Date", "Please enter a date in the future"); } break; case "TermsAccepted": if (!((bool)value)) { bindingContext.ModelState.AddModelError("TermsAccepted", "You must accept the terms"); } break; } } protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) { // make sure we call the base implementation base.OnModelUpdated(controllerContext, bindingContext); Appointment model = bindingContext.Model as Appointment; // apply our model-level validation if (model != null && bindingContext.ModelState.IsValidField("ClientName") && bindingContext.ModelState.IsValidField("Date") && model.ClientName == "Joe" && model.Date.DayOfWeek == DayOfWeek.Monday) { bindingContext.ModelState.AddModelError("", "Joe cannot book appointments on Mondays"); } } }
其中,OnModelUpdated方法是当给model所有属性赋值时触发,SetProperty方式是当单个属性变化时即触发。接下来要做的,就是在global的Application_Start方法中注册了:
ModelBinders.Binders.Add(typeof(Appointment), new ValidatingModelBinder());
然后,MakeBooking action就可以解脱出来,只需要如下几行代码:
if (ModelState.IsValid) { repository.SaveAppointment(appt); return View("Completed", appt); } else { return View(); }
此时,效果和第1种方式完全一样。
第3种方式是通过MetaData了。Asp.net MVC内置了5个meta data验证属性:Compare、Range、RegularExpression、Required、StringLength。基于这5个属性的一些限制,为了更适切Appointment类,自定义几个验证属性如下:
futureDate验证属性:
public class FutureDateValidatorAttribute : ValidationAttribute { public override bool IsValid(object value) { var isDate = value is DateTime; if(isDate) { var date = Convert.ToDateTime(value); if (date <= DateTime.Now) { return false; } } return true; } }
MustBeTrue验证属性:
public class MustBeTrueAttribute : ValidationAttribute { public override bool IsValid(object value) { return value is bool && (bool)value; } }
Appointment验证属性:
public class AppointmentValidatorAttribute : ValidationAttribute { public AppointmentValidatorAttribute() { ErrorMessage = "Joe cannot book appointments on Mondays"; } public override bool IsValid(object value) { Appointment app = value as Appointment; if (app == null || string.IsNullOrEmpty(app.ClientName) || app.Date == null) { // we don't have a model of the right type to validate, or we don't have // the values for the ClientName and Date properties we require return true; } else { return !(app.ClientName == "Joe" && app.Date.DayOfWeek == DayOfWeek.Monday); } } }
再来定义Appointment类:
[AppointmentValidator] public class Appointment { [Required(ErrorMessage = "Please enter your name")] public string ClientName { get; set; } [DataType(DataType.Date)] [FutureDateValidator(ErrorMessage = "You must enter a date in the future")] public DateTime Date { get; set; } //[Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the terms")] [MustBeTrue(ErrorMessage = "You must accept the terms")] public bool TermsAccepted { get; set; } }
第4种方式:通过实现IValidatableObject接口,定义自验证model。还是Appointment类,如下:
public class Appointment : IValidatableObject { public string ClientName { get; set; } [DataType(DataType.Date)] public DateTime Date { get; set; } public bool TermsAccepted { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { List<ValidationResult> errors = new List<ValidationResult>(); if (string.IsNullOrEmpty(ClientName)) { errors.Add(new ValidationResult("Please enter your name", new string[] { "ClientName" })); } if (DateTime.Now > Date) { errors.Add(new ValidationResult("Please enter a date in the future", new string[] { "Date" })); } if (errors.Count == && ClientName == "Joe" && Date.DayOfWeek == DayOfWeek.Monday) { errors.Add(new ValidationResult("Joe cannot book appointments on Mondays")); } if (!TermsAccepted) { errors.Add(new ValidationResult("You must accept the terms", new string[] { "TermsAccepted" })); } return errors; } }
可以看到,它的核心不过是:将类对象验证内容移入到Valiate方法中。
第5种方式,通过继承ModelValidationProvider,创建自定义ValidationProvider. 如下:
public class CustomValidationProvider : ModelValidatorProvider { public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context) { if (metadata.ContainerType == typeof(Appointment)) { return new ModelValidator[] { new AppointmentPropertyValidator(metadata, context) }; } else if (metadata.ModelType == typeof(Appointment)) { return new ModelValidator[] { new AppointmentValidator(metadata, context) }; } return Enumerable.Empty<ModelValidator>(); } }
AppointmentPropertyValidator代码如下:
public class AppointmentPropertyValidator : ModelValidator { public AppointmentPropertyValidator(ModelMetadata metadata, ControllerContext context) : base(metadata, context) { } public override IEnumerable<ModelValidationResult> Validate(object container) { Appointment appt = container as Appointment; if (appt != null) { switch (Metadata.PropertyName) { case "ClientName": if (string.IsNullOrEmpty(appt.ClientName)) { return new ModelValidationResult[] { new ModelValidationResult { //MemberName = "ClientName", Message = "Please enter your name" } }; } break; case "Date": if (appt.Date == null || DateTime.Now > appt.Date) { return new ModelValidationResult[] { new ModelValidationResult { MemberName = "", Message = "Please enter a date in the future" } }; } break; case "TermsAccepted": if (!appt.TermsAccepted) { return new ModelValidationResult[] { new ModelValidationResult { MemberName = "", Message = "You must accept the terms" } }; } break; } } return Enumerable.Empty<ModelValidationResult>(); } }
public class AppointmentValidator : ModelValidator { public AppointmentValidator(ModelMetadata metadata, ControllerContext context) : base(metadata, context) { } public override IEnumerable<ModelValidationResult> Validate(object container) { Appointment appt = (Appointment)Metadata.Model; if (appt.ClientName == "Joe" && appt.Date.DayOfWeek == DayOfWeek.Monday) { return new ModelValidationResult[] { new ModelValidationResult { MemberName = "", Message = "Joe cannot book appointments on Mondays" } }; } return Enumerable.Empty<ModelValidationResult>(); } }
做完这些工作,然后就是注册启用CustomerValidationProvider了。在Application_Start中加入:
ModelValidatorProviders.Providers.Add(new CustomValidationProvider());
就完毕了。
关于CustomerValidationProvider这种方式,作者建议仅用于复杂场合。如:需要从db中动态加载validation rule,或者实现自己的一些验证框架时才使用。这里有一个案例:http://www.codeproject.com/Articles/463900/Creating-a-custom-ModelValidatorProvider-in-ASP-NE
好吧,再看浏览器端的验证。
第1步先启用客户端验证:
<add key="ClientValidationEnabled" value="true"/> <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
或者在Application_Start中增加:
HtmlHelper.ClientValidationEnabled = true; HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
还有,view当中确保没有:
HtmlHelper.ClientValidationEnabled = false;
默认情况下,它是true。如果要禁用,上述3个区域任意一个设置为false即可。
第2步,view中加载4个必须文件:
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.5..min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
第3步,据说最简单的方式是利用meta data属性:
[AppointmentValidator] public class Appointment { [Required(ErrorMessage = "Please enter your name")] [StringLength(, MinimumLength = , ErrorMessage = "Please enter a string of whose length is between 3 and 10")] [EmailAddress] public string ClientName { get; set; } [DataType(DataType.Date)] [FutureDateValidator(ErrorMessage = "You must enter a date in the future")] public DateTime Date { get; set; } [MustBeTrue(ErrorMessage = "You must accept the terms")] public bool TermsAccepted { get; set; } }
其中EmailAddress是新实现的一个可供客户端验证用的metadata属性。如下:
public class EmailAddressAttribute : ValidationAttribute, IClientValidatable { private static readonly Regex emailRegex = new Regex(".+@.+\\..+"); public EmailAddressAttribute() { ErrorMessage = "Enter a valid email address"; } public override bool IsValid(object value) { return !string.IsNullOrEmpty((string) value) && emailRegex.IsMatch((string) value); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { return new List<ModelClientValidationRule> { new ModelClientValidationRule { ValidationType = "email", ErrorMessage = this.ErrorMessage }, //new ModelClientValidationRule // { // ValidationType = "required", // ErrorMessage = this.ErrorMessage // } }; } }
它实现了一个IClientValidatable
接口,所以能够直接在客户端交互。
关于它的实现原理,它不过是在server端将view上需要验证的全部信息都render并且隐藏在页面,然后基于jQuery的validation组件来交互。 看一下html片段:
<p> Your name: <input data-val="true" data-val-email="Enter a valid email address" data-val-length="Please enter a string of whose length is between 3 and 10" data-val-length-max="10" data-val-length-min="3" data-val-required="Please enter your name" id="ClientName" name="ClientName" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="ClientName" data-valmsg-replace="true"></span> </p> <p> Appointment Date: <input class="text-box single-line" data-val="true" data-val-remote="&#39;Date&#39; is invalid." data-val-remote-additionalfields="*.Date" data-val-remote-url="/Appointment/ValidateDate" data-val-required="The Date field is required." id="Date" name="Date" type="text" value="2012/10/16" /> <span class="field-validation-valid" data-valmsg-for="Date" data-valmsg-replace="true"></span> </p>
所以,在客户端,其实你可以脱离mvc框架自己来写。如:
$(document).ready(function () {$('form').validate({errorLabelContainer: '#validtionSummary',wrapper: 'li',rules: {ClientName: {required: true,}},messages: {ClientName: "Please enter your name"}});});
同时,在view中render时,你也可以按照自己的方式来做。如将原有的ClientName显示方式换为:
<p> Your name: @Html.TextBoxFor(m => m.ClientName, new { data_val = "true",data_val_email = "Enter a valid email address", data_val_required = "Please enter your name"}) @Html.ValidationMessageFor(m => m.ClientName) </p>
因为-在C#中是非法变量名字符,所以用_替代,同时asp.net mvc生成html时会将它替换为-。
最后一个问题是,当客户端验证需要使用服务器端资源时,怎么办? 这时就要使用到Remote Validation了。首先,自然是得后端有一个ajax调用的action了:
public JsonResult ValidateDate(string Date) { DateTime parsedDate; if (!DateTime.TryParse(Date, out parsedDate)) { return Json("Please enter a valid date (mm/dd/yyyy)", JsonRequestBehavior.AllowGet); } else if (DateTime.Now > parsedDate) { return Json("Please enter a date in the future", JsonRequestBehavior.AllowGet); } else { return Json(true, JsonRequestBehavior.AllowGet); } }
然后,在Appointment类的Date属性上加个Remote特性:
[DataType(DataType.Date)] //[FutureDateValidator(ErrorMessage = "You must enter a date in the future")] [Remote("ValidateDate", "Appointment")] public DateTime Date { get; set; }
至此,它就完成了。当你输入date结束后,就会调用ValidateDate(string Date)方法。 我在想,这里Appointment得是真正的ViewModel了,要不然就太别扭了。因为它实际上是调用了controller的action方法了。
全部源码download
Model Validation in Asp.net MVC的更多相关文章
- Model Validation in ASP.NET Web API
Model Validation in ASP.NET Web API 原文:http://www.asp.net/web-api/overview/formats-and-model-binding ...
- [转]Creating an Entity Framework Data Model for an ASP.NET MVC Application (1 of 10)
本文转自:http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/creating-a ...
- Model Validation in ASP.NET Web API By Mike Wasson|July 20, 2012 268 of 294 people found this helpful
using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using ...
- manually Invoking Model Binding / Model Binding /Pro asp.net mvc 5
限制绑定器 数据源
- ASP.NET MVC模型绑定的6个建议(转载)
ASP.NET MVC模型绑定的6个建议 发表于2011-08-03 10:25| 来源博客园| 31 条评论| 作者冠军 validationasp.netmvc.netasp 摘要:ASP.NET ...
- [转]ASP.NET MVC 2: Model Validation
本文转自:http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx?CommentPo ...
- 转载:Unobtrusive JavaScript in ASP.NET MVC 3 隐式的脚本在MVC3
Unobtrusive JavaScript 是什么? <!--以下是常规Javascript下写出来的Ajax--> <div id="test"> &l ...
- [引]ASP.NET MVC 4 Content Map
本文转自:http://msdn.microsoft.com/en-us/library/gg416514(v=vs.108).aspx The Model-View-Controller (MVC) ...
- ASP.NET MVC Framework
ASP.NET MVC Framework是微软在ASP.NET中所添加的一组类库,这组类库可以使用Model-View-Controller的设计模式来开发ASP.NET的应用程序.它与现有的ASP ...
随机推荐
- docker 现实---联网多台物理主机,容器桥到物理网络(三)
docker 默认桥接卡docker0 只有当这个单元中的所有容器桥接卡.例如,在主机虚拟网络适配器容器看通常称为veth*** 和docker只要把这些卡桥接在一起,例如下面的附图: waterm ...
- Ini文件帮助类
.ini文件是什么 .ini 文件是Initialization File的缩写,就是初始化文件.在Windows系统中,其是配置文件所采用的存储格式(主要是system.ini,win.ini,sy ...
- 64位操作系统下用Microsoft.Jet.OLEDB.4.0出现未注册错误
在WIN7 64位下用Microsoft.Jet.OLEDB.4.0方法访问数据库Access,出现未注册错误 如果是要建立64位的应用程序 1.Microsoft Access Database E ...
- UVA714- Copying Books(最大最小化)
意甲冠军:k手稿的部分成m部分,使每一个和最小 思路:典型最大值最小化问题,使用贪心+二分. 贪心的是每次尽量将元素往右边划分,二分查找最小的x满足m个连续的子序列和S(i)都不超过x. 由于输出的原 ...
- 有关windows在调试ODOO8.0有些问题
继Ubuntu建筑物8.0调试环境后,,尝试windows设置开发环境. 最后的调试和开发,或将返回Linux环境,由于前一段时间手贱,改变分区表,该grub搞哈.哎!后来重建mbr,手动检索分区表( ...
- HDOJ 1753 明朝A+B
http://acm.hdu.edu.cn/showproblem.php? pid=1753 大明A+B Time Limit: 3000/1000 MS (Java/Others) M ...
- 【Unity 3D】学习笔记29:游戏的例子——简单的小制作地图
无论学习.只看不练是坏科学. 因此,要总结回想这怎么生产MMROPG小地图的游戏.于MMROPG游戏类,在游戏世界中行走时导致各地,通常在屏幕的右上角,将有一个区域,以显示当前的游戏场景微缩.在游戏世 ...
- Vim 命令 【转】
高级一些的编辑器,都会包含宏功能,vim当然不能缺少了,在vim中使用宏是非常方便的: :qx 开始记录宏,并将结果存入寄存器xq 退出记录模式@x 播放记录在x寄存器中的宏命 ...
- Display Database Image using MS SQL Server 2008 Reporting Services
原文 Display Database Image using MS SQL Server 2008 Reporting Services With the new release of MS SQL ...
- apk当安装程序将文件复制到手机自带的指定文件夹
项目已获得,今天.apk文件以获得另一个非调试手机,发现一个问题. 由于涂料.所以绘图数据的点存储在一个.txt文字档.把它用usb传到指定目录下的,可是明显不科学,由于用户下载了你的.apk文件,你 ...