[ASP.NET MVC 小牛之路]16 - Model 驗證

上一篇博文 [ASP.NET MVC 小牛之路]15 - Model Binding 中講了MVC在Model Binding過程當中如何根據用戶提交HTTP請求數據建立Model對象。在實際的項目中,咱們須要對用戶提交的信息進行驗證。MVC 對驗證提供了較好的支持,如能夠經過 Model 元數據設置驗證規則、用 ModelState 來處理錯誤信息等。本文將介紹 Model 的各類驗證及其使用。雖然 Model 驗證使用起來很簡單,但爲了更深刻的理解它,強烈建議你們在閱讀本文前先閱讀 [ASP.NET MVC 小牛之路]15 - Model Bindingcss

本文目錄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

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" />
}

運行程序,效果以下:

Model Binder 提供的驗證

除了在 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會像前一節的例子那樣使用默認的消息。

自定義驗證特性類

繼承 ValidationAttribute

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 級驗證特性

上面咱們建立的自定義驗證特性都是應用在屬性上的,這就限制了驗證的規則只能和當前這個屬性相關。若是 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 的自驗證,即在 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引用:

  • /Scripts/ jquery-1.7.1.min.js
  • /Scripts/ jquery.validate.min.js
  • /Scripts/ jquery.validate.unobtrusive.min.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 驗證

最後要介紹的一種驗證是使用 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》

相關文章
相關標籤/搜索