上一篇博文 [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》