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


模型驗證是在模型綁定時檢查從HTTP請求接收的數據是否合規以保證數據的有效性,在收到無效數據時給出提示幫助用戶糾正錯誤的數據。

顯式模型驗證

驗證數據最直接的方式就是在action方法中對接收的數據驗證,以下面的Model為例:

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視圖中收集數據:

@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方法中驗證請求的數據:

... 
[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中:

... 
.input-validation-error { 
border: 1px solid #f00; 
background-color: #fee; 
} 
... 

其效果就是使得輸入控件邊框變紅、背景變粉紅以提示用戶有錯誤發生。如果自己編寫的HTML幫助函數要支持驗證錯誤提示,可以參考System.Mvc.Web.Html.InputExtensions的源代碼是如何實現的。

一些瀏覽器比如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有幾種重載形式:

重載形式 說明
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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM