模型驗證是在模型綁定時檢查從HTTP請求接收的數據是否合規以保證數據的有效性,在收到無效數據時給出提示幫助用戶糾正錯誤的數據。javascript
驗證數據最直接的方式就是在action方法中對接收的數據驗證,如下面的Model爲例:css
public class Appointment { public string ClientName { get; set; } public DateTime Date { get; set; } public bool TermsAccepted { get; set; } }
咱們要求ClientName不能爲空;約會日期Date不能早於當前日期,日期的格式能夠在web.config中使用<globalization culture="en-US" uiCulture="enUS"/>來指定,不然使用服務器默認的時區格式;TermsAccepted必須爲true。咱們在MakeBooking.cshtml視圖中收集數據:html
@model ModelValidation.Models.Appointment @{ ViewBag.Title = "Make A Booking"; } <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" /> }
直接在action方法中驗證請求的數據:java
... [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) { // statements to store new Appointment in a // repository would go here in a real project return View("Completed", appt); } else { return View(); } } ...
ModelState.IsValidField()檢查模型綁定器可否成功綁定「Date」屬性,若是數據不合法使用ModelState.AddModelError()添加錯誤消息。若是沒有任何錯誤,ModelState.IsValid=true,咱們能夠繼續正常操做,不然返回數據輸入界面。HTML.EditFor()幫助函數會檢查ModelState是否包含當前屬性的錯誤,若是有錯誤會爲生成的元素添加CSS類input-validation-error,默認的input-validation-error類定義在~/Content/Site.css中:jquery
... .input-validation-error { border: 1px solid #f00; background-color: #fee; } ...
其效果就是使得輸入控件邊框變紅、背景變粉紅以提示用戶有錯誤發生。若是本身編寫的HTML幫助函數要支持驗證錯誤提示,能夠參考System.Mvc.Web.Html.InputExtensions的源代碼是如何實現的。web
一些瀏覽器好比Chrome和Firefox會忽略應用在複選框Checkbox上的CSS屬性,咱們能夠經過前面講到的自定義模板來解決:正則表達式
@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) } }
這裏定義了一個bool類型專用的自定義模板,使用div標籤包裝checkbox,從modelstate檢查當前屬性是否有錯誤,有錯誤時添加錯誤提示的CSS類到div標籤上。瀏覽器
除了經過CSS風格提示錯誤,咱們能夠將添加到modelstate的錯誤消息在視圖中顯示給用戶:服務器
@model ModelValidation.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)</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" /> }
Html.ValidationSummary()幫助函數將ModelState中的錯誤消息以列表的方式羅列出來顯示給用戶。ValidationSummary有幾種重載形式:app
重載形式 | 說明 |
Html.ValidationSummary() | 彙總顯示全部的驗證錯誤 |
Html.ValidationSummary(bool) | 若是bool參數=true,只顯示Model層次的錯誤,不然全部的驗證錯誤都顯示 |
Html.ValidationSummary(string) | 在全部錯誤消息以前再顯示string給出的字符串 |
Html.ValidationSummary(bool, string) | 同Html.ValidationSummary(bool),只是在錯誤消息前多顯示string給出的字符串 |
所謂Model層次的錯誤,其實就是使用ModelState.AddModelError()添加錯誤消息時第一個表明錯誤屬性的參數留空,好比:
... if (ModelState.IsValidField("ClientName") && ModelState.IsValidField("Date") && appt.ClientName == "Joe" && appt.Date.DayOfWeek == DayOfWeek.Monday) { ModelState.AddModelError("", "Joe cannot book appointments on Mondays"); } ...
除了Html.ValidationSummary(),咱們能夠將錯誤消息緊鄰輸入控件挨個顯示:
@model ModelValidation.Models.Appointment @{ ViewBag.Title = "Make A Booking"; } <h4>Book an Appointment</h4> @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" /> }
Html.ValidationMessageFor()在屬性有錯誤時顯示對應的錯誤消息,爲避免在彙總消息中重複顯示,這裏使用true參數調用Html.ValidationSummary(true)。
默認模型綁定器DefaultModelBinder內建在綁定時驗證數據,好比咱們輸入非日期格式給Date屬性,綁定器會給出「The value 'xxx' is not valid for Date.」的錯誤消息。咱們能夠重載DefaultModelBinder的一些方法來添加有用的信息:
方法 | 說明 | 默認實現的功能 |
OmModelUpdated | 在綁定器試圖給模型對象全部屬性賦值時調用 | 根據模型metadata給出的驗證規則驗證數據添加錯誤消息到ModelState |
SetProperty | 在綁定器視圖給模型對象的某個屬性賦值時調用 | 若是模型屬性不能是Null可是沒有數據來綁定時添加「The <name> field is required」消息到ModelState,若是有數據可是處理錯誤好比類型轉換失敗添加「The value <value> is not valid for <name>」消息到ModelState |
更多的時候咱們不須要重載默認模型綁定器,由於咱們能夠更方便的使用metadata在數據模型上添加驗證規則:
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; } }
這裏使用了Required和Range兩個特性,前者表示數據是必須的,後者指定了一個可用值範圍;ErrorMessage則是錯誤時的提示消息,若是不指定則使用上表中的默認消息。可用的驗證特性包括:
特性 | 示例 | 說明 |
Compare | [Compare("MyOtherProperty")] | 兩個屬性必須相同值,好比咱們要求用戶重複輸入兩次郵件地址時有用 |
Range | [Range(10, 20)] | 屬性值必須在指定的數值範圍內,能夠使用數值類型的最大最小值好比int.MinValue、int.MaxValue |
RegularExpression | [RegularExpression("pattern")] | 字符串值必須匹配正則表達式,默認大小寫敏感,能夠使用(?i)修飾符關閉大小寫敏感,好比[RegularExpression("(?i)mypattern")] |
Required | [Required] | 屬性值必須非空或者不能只是空格,若是容許全空格能夠[Required(AllowEmptyStrings = true)] |
StringLength | [StringLength(10)] | 字符串長度不能超過給定的最大長度,也能夠指定最小長度:[StringLength(10, MinimumLength=2)] |
上面的例子中沒有使用Required特性驗證bool類型的TermsAccepted,這是由於EditFor()在渲染Checkbox會多給出一個hidden的輸入元素,即便咱們沒有選中checkbox返回的結果中仍然是有值的。使用Range看上去比較彆扭,好在咱們能夠建立自定義的驗證特性類來改進:
public class MustBeTrueAttribute : ValidationAttribute { public override bool IsValid(object value) { return value is bool && (bool)value; } }
這裏驗證輸入數據是不是bool類型且爲true,使用這個自定義驗證特性很簡單:
.. [MustBeTrue(ErrorMessage="You must accept the terms")] public bool TermsAccepted { get; set; } ...
除了從ValidationAttribute擴展自定義特性,咱們能夠直接從內建的驗證特性擴展:
public class FutureDateAttribute : RequiredAttribute { public override bool IsValid(object value) { return base.IsValid(value) && ((DateTime)value) > DateTime.Now; } }
這裏從Require驗證特性擴展,在調用基類的驗證後再作附加的檢查。
以上的驗證特性都是針對模型單個屬性的,咱們還能夠爲整個模型建立自定義驗證特性:
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) { // 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); } } }
這裏檢查客戶名稱和約定日期,不容許客戶名稱Joe在星期一預定,咱們能夠將這個特性應用在整個模型類上:
[NoJoeOnMondays] public class Appointment { [Required] public string ClientName { get; set; } [DataType(DataType.Date)] [FutureDate(ErrorMessage="Please enter a date in the future")] public DateTime Date { get; set; } [MustBeTrue(ErrorMessage="You must accept the terms")] public bool TermsAccepted { get; set; } }
有了這些驗證規則特性,控制器類的action方法能夠極大的簡化爲:
... [HttpPost] public ViewResult MakeBooking(Appointment appt) { if (ModelState.IsValid) { // statements to store new Appointment in a // repository would go here in a real project return View("Completed", appt); } else { return View(); } } ...
模型驗證的另一種是爲模型類實現IValidatableObject接口建立可自驗證的模型類:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using ModelValidation.Infrastructure; namespace ModelValidation.Models { 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; } } }
模型綁定器在試圖給模型對象賦值時調用Validate(),返回結果是一個錯誤列表,使用這種方式咱們能夠在一個地方作完全部的數據驗證。
以上講的都是數據提交到服務器上後的驗證,客戶端咱們能夠經過腳本在數據提交到服務器前驗證,MVC支持「unobtrusive client-side validation」,unobtrusive意指在輸出HTML標籤時添加特定HTML標籤,過MVC的JAVA腳本驗證庫利用這些專用特性進行數據驗證。要使用客戶端驗證首先須要在web.config中啓用:
... <appSettings> <add key="ClientValidationEnabled" value="true"/> <add key="UnobtrusiveJavaScriptEnabled" value="true"/> </appSettings> ...
上面的兩個設置必須都爲true,咱們能夠在Razor代碼塊中使用HtmlHelper.ClientValidationEnabled和HtmlHelper.UnobtrusiveJavaScriptEnabled爲單個視圖配置是否使用客戶端驗證。咱們還必須保證如下腳本文件被添加到視圖或者佈局文件中:
能夠看到客戶端驗證仍然是依賴於Jquery的。在啓用客戶端驗證後,咱們添加到模型類上的內建驗證特性好比Requried、StringLength就能夠直接工做了,數據驗證錯誤時java腳本會給出錯誤提示。具體來說EditFor()這些模板幫助函數在啓用客戶端驗證後會輸出一些額外的特性,好比上面ClientName屬性:
... <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="" /> ...
JQuery驗證函數查找data-val=true的元素進行驗證,data-val-<xxx>則是具體的驗證規則,好比這裏的data-val-length和data-val-required。經過元數據指定的驗證規則既能夠在客戶端使用,也能夠在服務器端使用,爲咱們帶來了極大的方便,並且即便在客戶端禁用了JAVA腳本,服務器端的數據驗證仍然有效。
遠程驗證是客戶端驗證和服務端驗證的折中方式,客戶端在背後經過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); } } ...
驗證action方法必須有一個和要驗證字段同名的參數,這裏定義Date爲字符串類型是有考慮的。模型綁定若是不能從請求數據中轉換成日期類型會發生異常,遠程驗證沒法在客戶端顯示異常信息會被靜悄悄的丟棄,因此通常咱們使用字符串類型,在驗證方法內部顯式的轉換數據類型。驗證方法返回一個返回一個JsonResult對象,驗證成功咱們封裝true,不成功封裝錯誤信息,不管哪一種結果咱們使用JsonRequestBehavior.AllowGet標識驗證結果能夠經過GET請求。
有了遠程驗證action,咱們須要添加remote驗證特性到相應的模型類屬性上:
public class Appointment { [Required] [StringLength(10, MinimumLength = 3)] public string ClientName { get; set; } [DataType(DataType.Date)] [Remote("ValidateDate", "Home")] public DateTime Date { get; set; } public bool TermsAccepted { get; set; } }
在Remote驗證特性中指定用於驗證的控制器名稱和action,MVC的javascript驗證庫按今生成的URL請求並驗證。遠程驗證會在用戶第一次提交表單時生效,以及此後的每一次編輯數據的動做,好比每一次的按鍵都會執行一次遠程驗證,這是咱們在帶寬有限時須要考慮的。
以上爲對《Apress Pro ASP.NET MVC 4》第四版相關內容的總結,不詳之處參見原版 http://www.apress.com/9781430242369。