上一篇博文 [ASP.NET MVC 小牛之路]15 - Model Binding 中讲了MVC在Model Binding过程当中如何根据用户提交HTTP请求数据建立Model对象。在实际的项目中,咱们须要对用户提交的信息进行验证。MVC 对验证提供了较好的支持,如能够经过 Model 元数据设置验证规则、用 ModelState 来处理错误信息等。本文将介绍 Model 的各类验证及其使用。虽然 Model 验证使用起来很简单,但为了更深刻的理解它,强烈建议你们在阅读本文前先阅读 [ASP.NET MVC 小牛之路]15 - Model Binding。css
本文目录html
按照惯例,先建立一个MVC应用程序(基本模板)。建立一个名为 Appointment 的Model,代码以下:jquery
using System; using System.ComponentModel.DataAnnotations; namespace MvcApplication1.Models { public class Appointment { public string ClientName { get; set; } [DataType(DataType.Date)] public DateTime Date { get; set; } public bool TermsAccepted { get; set; } } }
再建立一个Controller,添加 MakeBooking Action,以下:浏览器
public class HomeController : Controller { public ViewResult MakeBooking() { return View(new Appointment { Date = DateTime.Now }); } [HttpPost] public ViewResult MakeBooking(Appointment appt) { return View("Completed", appt); } }
而后为两个版本的MakeBooking Action方法分别添加两个View,一个 MakeBooking.cshtml :app
@model MvcApplication1.Models.Appointment <h4>Book an Appointment</h4> @using (Html.BeginForm()) { <p>Your name: @Html.EditorFor(m => m.ClientName)</p> <p>Appointment Date: @Html.EditorFor(m => m.Date)</p> <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p> <input type="submit" value="Make Booking" /> }
和一个Completed.cshtml:框架
@model MvcApplication1.Models.Appointment <h4>Your appointment is confirmed</h4> <p>Your name is: <b>@Html.DisplayFor(m => m.ClientName)</b></p> <p>The date of your appointment is: <b>@Html.DisplayFor(m => m.Date)</b></p>
ModelState 是 Controller 抽象类的一个属性,它是 MVC 处理完验证时要使用的一核心对象,提供了对验证结果的存、取和判断。因此验证用户提交的数据,最直接的方法是在Action方法中使用 ModelState 对Model对象的属性值自行判断合法性。下面用一个示例来讲明。ide
修改带 Appointment 类型参数的 MakeBooking action 方法,代码以下:post
[HttpPost] public ViewResult MakeBooking(Appointment appt) { if (string.IsNullOrEmpty(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.IsValid) { return View("Completed", appt); } else { return View(); } }
在这咱们经过 ModelState 检查被Model Binder赋过值的参数对象,若是对象的属性值不合法则经过 ModelState.AddModelError 方法添加一个错误信息。ModelState.IsValidField 方法用于检查用户提交的值是否可以被Model Binder成功赋值给指定的属性。若都未经过验证,则从新呈现 MakeBooking.cshtml 视图,View 会根据 ModelState 中的错误信息给对应的 input 添加一个 input-validation-error 样式类,该样式类在默认引用的 /Content/Site.css 下的定义为:ui
.input-validation-error { border: 1px solid #f00; background-color: #fee; }
运行效果和生成的 Html 代码以下:url
这会就有个疑问了,勾选框和文本框都应用了 input-validation-error 样式类,为何勾选框就没有效果呢。其实大部分主流浏览器(包括Chrome 和 Firefox)都会忽略单元框上的样式。在前面的博文 [ASP.NET MVC 小牛之路]13 - Helper Method 中咱们知道了如何自定义 Helper Method 模板,对于勾选框没有样式的问题,咱们就能够经过自定义 Helper Method 模板解决这个问题,在 /Views/Shared/EditorTemplates 文件夹下建立一个 Boolean.cshtml 分部视图,代码以下:
@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 > 0) { <div class="input-validation-error" style="float:left"> @Html.CheckBox("", value) </div> } else { @Html.CheckBox("", value) } }
再次运行程序,能够看到勾选框也有了框色的边框,效果以下:
样式是为了让用户快速地定位到没有正确输入的地方,另外,对用户提交欲提交的数据进行验证完后,还应该对没有经过验证的字段有给予消息提示。验证消息的显示,能够简单的分为两种,一种是Model级的,另外一种是属性级的,咱们先来看Model级的。
咱们在 MakeBooking.cshtml 视图中加入一句 @Html.ValidationSummary() 代码,以下:
... @using (Html.BeginForm()) { @Html.ValidationSummary() <p>Your name: @Html.EditorFor(m => m.ClientName)</p> ... }
运行效果和生成的验证消息HTML代码分别以下:
一样,在 /Content/Site.css 文件中也定义了 validation-summary-errors 样式类,以下:
.validation-summary-errors { font-weight: bold; color: #f00; }
Html.ValidationSummary() 还有三些重载方法:Html.ValidationSummary(bool) 、 Html.ValidationSummary(string) 和 Html.ValidationSummary(bool, string) 。第一个是当参数为true时,只显示Model级的验证消息(若是 ModelState.AddModelError 方法的第一个参数没有指定属性名称,则为Model级的),第二个是为全部的验证消息显示一个标题,第三个是前两个的结合。
至于属性级的验证消息显示,也很简单,使用方法以下:
@using (Html.BeginForm()) { @Html.ValidationSummary(true) <p>@Html.ValidationMessageFor(m => m.ClientName)</p> <p>Your name: @Html.EditorFor(m => m.ClientName)</p> <p>@Html.ValidationMessageFor(m => m.Date)</p> <p>Appointment Date: @Html.EditorFor(m => m.Date)</p> <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p> <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p> <input type="submit" value="Make Booking" /> }
运行程序,效果以下:
除了在 Action 方法中进行验证,默认的 Model Binder (DefaultModelBinder 类)在对 Model 绑定值时也有验证的处理。下面咱们来看看它实现验证的效果。
把 Action 中的 ModelState.AddModelError 方法都删除,删除后以下:
[HttpPost] public ViewResult MakeBooking(Appointment appt) { if (ModelState.IsValid) { return View("Completed", appt); } else { return View(); } }
运行程序,能够看到默认的 Model Binder 实现的验证结果以下:
当默认的Model Binder不可以从提交的表单元素的值中建立一个 DateTime 类型的对象时,则会为 Date 字段添加一个错误(字段不能为空)。默认的 Model Binder 为 Model 对象的每一个属性提供了一些基本的验证处理。例如,对于值类型,若是Binder未能给它绑定到值,它会把错误信息添加到ModelState中,而后由 Helper Method 为该字段显示相应的错误消息。
默认的Model Binder(DefaultModelBinder 类)提供了一些给Binder添加验证处理的可重写方法。如 OmModelUpdated 和 SetProperty,前者在Binder为Model的全部属性赋值后执行,后者在Binder为属性赋值时执行。当咱们经过继承 DefaultModelBinder 来自定义 Model Binder时,则能够重写这些方法来实现一些特殊的验证需求。关于自定义 Model Binder 请阅读本系列的 [ASP.NET MVC 小牛之路]15 - Model Binding 文章。
但对于MVC模式来讲,若是把验证的规则放在自定义的 Model Binder 类中彷佛并不合适。更多的时候咱们会选择使用元数据的方式把验证的规则放在Model类中。
MVC 框架支持使用元数据来表示Model验证的规则。相对于在 Action 方法中的验证,使用元数据的好处在于能使某个Model的验证规则应用于整个应用程序。DefaultModelBinder 在绑定Model时,会检查该Model上提供了验证规则的特性元数据。你能够看到下面对 Appointment model 应用的验证规则特性:
public class Appointment { [Required] public string ClientName { get; set; } [DataType(DataType.Date)] [Required(ErrorMessage="Please enter a date")] public DateTime Date { get; set; } [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the terms")] public bool TermsAccepted { get; set; } }
下面列出了MVC内置的验证特性:
全部的用于验证的特性均可以像下这样指定错误消息:
[Required(ErrorMessage="Please enter a date")]
若是没有指定错误消息,MVC会像前一节的例子那样使用默认的消息。
MVC 内置的用于验证特性是一些经常使用的,当这些特性不能知足咱们的需求时,咱们能够经过继承 ValidationAttribute 类自定义一个特性。例如,在上面的 Appointmen 中用的是 Range 特性来保证 TermsAccepted 的值必须为 true,这看起来很怪,咱们能够为此自定义一个特性。
添加一个 Infrastructure 文件夹,在该文件夹中添加一个名为 MustBeTrueAttribute 的类,代码以下:
public class MustBeTrueAttribute : ValidationAttribute { public override bool IsValid(object value) { return value is bool && (bool)value; } }
这个特性类重写了基类的 IsValid 方法,Model Binder 将使用这个特性类来验证应用了该特性的属性的值。这个类的验证逻辑很简单,即若是是 true 值则经过验证。而后咱们在 Appointment model中对 TermsAccepted 属性应用该特性,以下:
... [MustBeTrue(ErrorMessage="You must accept the terms")] public bool TermsAccepted { get; set; } ...
这样看起来比使用Range更简洁易读。运行效果以下:
每一个内置的特性类都是继承自 ValidationAttribute 类,都有一个能够被重写的 IsValid 方法,因此咱们也能够经过继承内置的特性类来自定义。为此,咱们再举个例子。
在 Infrastructure 文件下添加一个名为 FutureDateAttribute 的类,代码以下:
public class FutureDateAttribute : RequiredAttribute { public override bool IsValid(object value) { return base.IsValid(value) && ((DateTime)value) > DateTime.Now; } }
将此特性应用到 Appointment model的 Date 属性上,以下:
[FutureDate(ErrorMessage="Please enter a date in the future")] public DateTime Date { get; set; }
这样咱们就能够实现 Date 属性值必须大于当前时间的验证。
上面咱们建立的自定义验证特性都是应用在属性上的,这就限制了验证的规则只能和当前这个属性相关。若是 Model 中的多个属性准定了一个验证规则。例如,Joe这我的星期一这天不能预定,这个验证规与 ClientName 和 Date 两个属性相关,因此须要定义一个 Model 级的验证特性,下面演示如何定义 Model 级的验证特性。
在 Infrastructure 文件下添加一个名为 NoJoeOnMondaysAttribute 的类,代码以下:
public class NoJoeOnMondaysAttribute : ValidationAttribute { public NoJoeOnMondaysAttribute() { 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) { return true; } else { return !(app.ClientName == "Joe" && app.Date.DayOfWeek == DayOfWeek.Monday); } } }
把这个特性应用在 Appointment model上,以下:
[NoJoeOnMondays] public class Appointment { ... }
右键浏览 MakeBooking 视图,效果以下:
另外一个验证技术是 Model 的自验证,即在 Model 类内部编写验证逻辑方法,经过实现 IValidatableObject 接口来告诉 MVC 该某个 Model 是否为自验证的 Model。
下面咱们让 Appointment model 实现 IValidatableObject 接口使它包含自验证功能:
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")); } if (DateTime.Now > Date) { errors.Add(new ValidationResult("Please enter a date in the future")); } if (errors.Count == 0 && 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")); } return errors; } }
IValidatableObject 接口只定义了一个方法,Validate。该方法的返回值是一个 ValidationResult 类型的集合,每一个 ValidationResult 对象表明一个验证错误。若是一个 Model 实现了 IValidatableObject 接口,MVC 会在 Model Binder 为 Model 的每一个属性赋值后调用Validate方法。相对于在 action 方法中的验证,这种 Model 自验证更为灵活,并且把验证逻辑放在对应的Model中,保证了代码的一致性,方便维护。最后来看来运行结果:
客户端验证在Web.config中有两个开关,默认都是启用的,以下:
... <appSettings> <add key="ClientValidationEnabled" value="true"/> <add key="UnobtrusiveJavaScriptEnabled" value="true"/> </appSettings> ...
要启用客户端验证,这两个值都须要设为true。你也能够在单个的View中经过设置HtmlHelper.ClientValidationEnabled 和 HtmlHelper.UnobtrusiveJavaScriptEnabled的值来开启或关闭客户端验证。启用时还须要包含三个JS引用:
添加这些引用最简单的方法是使用MVC 4新加的一个叫捆绑的功能(将在后续博文中介绍),以下在 /Views/Shared/_Layout.cshtml 文件中下面的代码和引用以上三个文件是同样的:
<body> @RenderBody() @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/jqueryval") @RenderSection("scripts", required: false) </body>
当咱们启用客户端验证后,要使用起来,最简单的方即是对Model应用验证特性,如Required、Range等。为了演示,咱们修改 Appointment 类以下:
public class Appointment { [Required] [StringLength(10, MinimumLength = 3)] public string ClientName { get; set; }
[DataType(DataType.Date)] public DateTime Date { get; set; }
public bool TermsAccepted { get; set; } }
这样作就能够了,运行程序,在 name 字段输入框随便输入一个字符,则即刻出现错误消息,以下所示:
这里的验证规则是经过后台指定的。但并非全部后台使用的验证都有对应的客户端验证,例如 action 中的验证、应用Model级的验证特性和Model的自验证都是没有客户端验证的。
使用 MVC 提供的客户端验证的好处之一是不用写 JavaScript 代码。它的工做方法相似于 [ASP.NET MVC 小牛之路]14 - Unobtrusive Ajax 文章中的 Unobtrusive Ajax,MVC 经过生成 HTML 属性来表示验证规则。若是没有启用客户端验证,@Html.EditorFor(m => m.ClientName) 生成的 HTML 代码是:
<input class="text-box single-line" id="ClientName" name="ClientName" type="text" value="" />
启用客户端验证生成的 HTML 代码是:
<input class="text-box single-line" data-val="true" data-val-length="The field ClientName must be a string with a minimum length of 3 and a maximum length of 10."
data-val-length-max="10" data-val-length-min="3" data-val-required="The ClientName field is required."
id="ClientName" name="ClientName" type="text" value="" />
引入的两个客户端验证的 jQurey 库根据 data-val 的属性值来判断HTML元素是否须要验证,而验证规则是被名称为 data-val-<name> 的属性指定的,<name> 表明的是规则名(如data-val-length-max),而后根据这些个属性的值来实现具体的验证规则。
MVC 客户端验证的另外一个好处是,用户能够即时的看到验证消息,更快地获得反馈。固然,若是用户禁用了JavaScript, MVC 就会走后台验证。
你也能够不使用 MVC 特性来实现客户端验证,若是你愿意花时间研究一下 jquery.validate.js ,也能够很方便地实现客户端验证。
最后要介绍的一种验证是使用 Remote 验证。这种验证明际上就是经过 Ajax 实现的,只是被MVC封装好了,用起来简单多了,也不须要写 JavaScript 代码。下面经过具体的例子说明 Remote 验证的用法。
在 HomeController 中添加一个用于 Remote 验证的 Action 方法,代码以下:
public JsonResult ValidateDate(string Date) { DateTime parsedDate; if (!DateTime.TryParse(Date, out parsedDate)) { return Json("Please enter a valid date (yyyy/mm/dd)", JsonRequestBehavior.AllowGet); } else if (DateTime.Now > parsedDate) { return Json("Please enter a date in the future", JsonRequestBehavior.AllowGet); } else { return Json(true, JsonRequestBehavior.AllowGet); } }
用于 Remote 验证的Action 方法必须返回一个 JsonResult 类型的结果,至于 Json 方法为何要指定第二个参数为 JsonRequestBehavior.AllowGet 请看 [ASP.NET MVC 小牛之路]14 - Unobtrusive Ajax 文章。
而后在 Appointment model 的 Date 属性上应用 Remote 特性,须要指定实施验证规则的 Action 方法名和 Controller 名,以下:
public class Appointment {public string ClientName { get; set; }
[DataType(DataType.Date)] [Remote("ValidateDate", "Home")] public DateTime Date { get; set; }
public bool TermsAccepted { get; set; } }
运行程序,效果以下:
效果上和客户端验证差很少,但验证的处理是在 Controller 中的 Action 中发生的。应用 Remote 特性的字段,每次改变它的值都会调用一次后台,因此从某种意义上来讲,咱们应该尽可能避免使用这种验证,除了那种不得不与后台交互的验证,如检查一个用户名是否已经存在。
参考:《Pro ASP.NET MVC 4 4th Edition》