ASP.NET MVC 4 (十) 模型驗證

模型驗證是在模型綁定時檢查從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爲單個視圖配置是否使用客戶端驗證。咱們還必須保證如下腳本文件被添加到視圖或者佈局文件中:

  • /Scripts/ jquery-1.7.1.min.js
  • /Scripts/ jquery.validate.min.js
  • /Scripts/ jquery.validate.unobtrusive.min.js

能夠看到客戶端驗證仍然是依賴於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。

相關文章
相關標籤/搜索