數據註解和驗證 – ASP.NET MVC 4 系列

       不只在客戶端瀏覽器中須要執行驗證邏輯,在服務器端也須要執行。客戶端驗證能即時給出一個錯誤反饋(阻止請求發送至服務器),是時下 Web 應用程序所指望的特性。服務器端驗證,主要是由於來自網絡的信息都是不可信任的程序員

       當在 ASP.NET MVC 設計模式上下文中談論驗證時,主要關注的是驗證模型的值。ASP.NET MVC 驗證特性能夠幫助咱們驗證模型值,且這樣驗證特性是可擴展的,因此咱們能夠採用任意想要的方式構建驗證模式,默認方法是一種聲明式驗證,即數據註解特性正則表達式

       註解是一種通用機制,不僅僅侷限於驗證這一用途,能夠用來向框架注入元數據。數據庫

爲驗證註解訂單

購買音樂的顧客會有一個典型的購物車結算環節,須要付款和填寫收貨信息。Order 類包含完成結算所須要須要的完整信息:編程

public partial class Order
    {
        public int OrderId { get; set; }
        public System.DateTime OrderDate { get; set; }
        public string Username { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
        public string Phone { get; set; }
        public string Email { get; set; }
        public decimal Total { get; set; }
        public List<OrderDetail> OrderDetails { get; set; }
    }

       應用程序使用 HTML 輔助方法 EditorForModel 來構建結算頁面,下面是視圖中的代碼:設計模式

<fieldset>
 <legend>Shipping Information</legend>
    @Html.EditorForModel()
</fieldset>

image

       EditForModel 輔助方法爲對象的每一個屬性構建一個它認爲合適的編輯器(這類型的編程作法適用於快速開發且對頁面佈局和美觀度要求不高的內部小項目)。這個表單存在一些明顯的問題。好比 OderId 和 OderDate 編輯器,這些值並不須要用戶填寫,應用程序會在服務器端設置。FirstName 屬性對程序員有意義,而客戶會認爲難道少數如一個空格?(指正確寫法是 First Name)數組

驗證註解的使用

       數據註解特性定義在命名空間 System.ComponentModel.DataAnnotations 中(並不全是,下面會講到),它們提供了服務器端的驗證功能,當在模型的屬性上使用這些特性時,框架也支持客戶端驗證。瀏覽器

Required當屬性爲 null 或者爲空時,Required 特性會引起一個驗證錯誤。客戶的姓氏和名字都是必需的,因此能夠在模型上添加此屬性。服務器

[Required]
public string FirstName { get; set; }
 
[Required]
public string LastName { get; set; }

       全部內置的驗證特性既傳遞服務器端驗證邏輯也傳遞客戶端驗證邏輯,即便客戶端關閉了 JavaScript,服務器端也會引起驗證。網絡

       StringLength若是試圖向數據庫插入一個超過最大長度的字符串,就會引起異常,這就是 StringLength 的用武之地app

[Required]
[StringLength(160)]
public string FirstName { get; set; }
 
[Required]
[StringLength(160)]
public string LastName { get; set; }
       MinimumLength 是一個可選參數,若是設置了,意味着該字符串的驗證具備一個長度的範圍區間
[Required]
[StringLength(160, MinimumLength = 3)]
public string FirstName { get; set; }

       RegularExpression:一些屬性要求的驗證並不是是簡單的非空或長度範圍,例如 Email 屬性須要一個有效可用的 Email 地址(至少看上去是一個有效的地址)。這裏咱們使用正則表達式來使驗證輸入的 Email 地址:

[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}")]
public string Email { get; set; }

       Range該特性用來指定數值類型的最大值和最小值

[Range(18,70)]
public int Age { get; set; }

System.Web.Mvc 下的驗證特性

       ASP.NET MVC 框架還會應用程序在命名空間 System.Web.Mvc 中額外添加了兩個驗證特性。

       Remote能夠利用服務器端的回調函數執行客戶端的驗證邏輯。例如,系統中不容許兩個用戶具備相同的 UserName 值,但客戶端驗證是很難作到這一點的,使用 Remote 特性能夠把 UserName 的值傳到服務器(能夠指定操做名和控制器名),而後在服務器端進行驗證:

[Remote("CheckUserName","Account")]
public string Username { get; set; }
 
// Account 控制器中的驗證方法
public JsonResult CheckUserName(string username)
{
    var result = Membership.FindUsersByName(username).Count == 0;
    return Json(result, JsonRequestBehavior.AllowGet);        
}

       Compare它用來確保模型對象的兩個屬性擁有相同的值。例如購物時,要求用戶輸入兩次 Email 地址以確保用戶輸入無誤:

[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}")]
public string Email { get; set; }
 
[System.Web.Mvc.Compare("Email")]
public string EmailConfirm { get; set; }

自定義錯誤提示消息及本地化

       每一個驗證特性都容許傳遞一個帶有自定義錯誤提示消息的參數。例如 Email 的本來錯誤提示消息是一個正則表達式,在客戶看來就像是一串亂碼,此時自定義錯誤消息就派上了用處:

[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",
    ErrorMessage = "Email 格式不正確!")]
public string Email { get; set; }
 
[System.Web.Mvc.Compare("Email", ErrorMessage = "兩次輸入的密碼不一樣!")]
public string EmailConfirm { get; set; }

       自定義的錯誤提示消息在字符串中也帶有一個格式項。內置特性使用友好的屬性顯示名稱格式化錯誤提示消息字符串,注意,是屬性顯示名稱(後面會講到),若無,則直接使用屬性名填充字符串佔位符。

[Required(ErrorMessage="Your {0} is required.")]
[StringLength(160, MinimumLength = 3)]
public string LastName { get; set; }

註解的後臺原理

       ASP.NET MVC 的驗證特性是由模型綁定器、模型元數據、模型驗證器、模型狀態組成的協調系統的一部分。默認狀況下,ASP.NET MVC 框架在模型綁定時執行驗證邏輯,當操做方法帶有參數時,就會隱式的執行模型綁定,固然,也能夠調用 UpdateModel 或 TryUpdateModel 方法顯式執行模型綁定。

  1. 模型綁定器一旦使用新值完成對模型屬性的更新,就會利用當前的模型元數據得到模型的全部驗證器。
  2. ASP.NET MVC 運行時提供了一個驗證器 DataAnnotationsModelValidator 來於數據註解一同工做,它會找到全部的驗證特性並執行驗證邏輯。
  3. 模型綁定器捕獲全部失敗的驗證規則並把它們放入模型狀態中。

       模型綁定主要的副產品是模型狀態 ModelState,模型狀態不只包含了用戶想放入模型屬性裏的值,也包括與每一個屬性相關聯的全部錯誤。假設用戶在沒有填寫 LastName 值的狀況下提交了表單,因爲設置了 Required 驗證註解特性,所以模型綁定以後,下面全部的表達式都將返回 false:

bool flag;
flag = ModelState.IsValid;
flag = ModelState.IsValidField("LastName");
flag = ModelState["LastName"].Errors.Count > 0;

       也能夠在模型狀態中查看與失敗驗證相關的錯誤提示消息:

var errorMsg = ModelState["LastName"].Errors[0].ErrorMessage;

       不多會編寫代碼來查看特定的錯誤消息。以前的 HTML 輔助方法中也介紹過,輔助方法能夠利用模型狀態(和模型狀態中出現的錯誤)來改變模型在視圖中的顯示。例如,ValidationMessage 輔助方法能夠經過查看模型狀態來顯示與特定部分視圖數據相關的錯誤消息:

@Html.ValidationMessageFor(m => m.LastName)

控制器操做和驗證錯誤

       控制器決定了在模型驗證失敗和成功時的執行流程。當驗證成功時,操做一般會執行必要的步驟來保存和更新客戶的信息;當驗證失敗時,操做通常會從新渲染提交模型值的視圖。這樣就可讓用戶看到全部的驗證錯誤提示消息,並按照提示進行更正或補填遺漏的字段信息。

[HttpPost]
public ActionResult AddressAndPayment(Order newOrder)
{
    if (ModelState.IsValid)
    {
        newOrder.Username = User.Identity.Name;
        newOrder.OrderDate = DateTime.Now;
 
 // Store DB and do something...
 
        return RedirectToAction("Complete", new { id = newOrder.OrderId });
    }
    return View(newOrder);
}

       注意,若是模型狀態無效,操做就會從新渲染 AddressAndPayment 視圖,給用戶一個修正錯誤並從新提交表單的機會

自定義驗證邏輯

       數據註解特性給驗證帶來了簡易性和透明性,但也不可能知足程序中可能遇到的全部驗證場合。

       ASP.NET MVC 框架的擴展性意味着實現自定義驗證邏輯有着很大的可行性。這裏重點介紹兩個核心應用方法:

  1. 將驗證邏輯封裝在自定義的數據註解中。
  2. 將驗證邏輯封裝在模型對象中。

       封裝在自定義數據註解中,則能夠輕鬆實如今多個模型中重用邏輯,固然,這須要在特性內部編寫代碼以應對不一樣類型的模型,但一旦實現,新的註解就能夠在多處重用。

       將驗證邏輯直接放入模型對象中,就意味着驗證邏輯能夠很容易的編碼實現,由於這樣只需關心一種模型對象的驗證邏輯,但不利於重用。

自定義註解

       假設要限制客戶輸入姓氏中單詞的數量,並讓這種驗證在其餘模型中重用,那就能夠考慮將驗證邏輯封裝在一個可重用的特性中。全部的驗證註解特性最終都派生自基類 ValidationAttribute,它是個抽象類,在命名空間 System.ComponentModel.DataAnnotations 中定義,程序員的驗證邏輯也必須派生自該類

using System.ComponentModel.DataAnnotations;
 
namespace MvcMusicStore.ExtendValidationAttribute
{
    public class MaxWordsAttribute : ValidationAttribute
    {
 /// <summary>
 /// 爲了實現驗證邏輯,至少須要從新基類中 IsValid 方法的其中一個版本。
 /// 重寫 IsValid 方法時能夠利用的 ValidationContext 參數提供了不少可在 IsValid
 /// 內部使用的信息,如模型類型、模型對象實例、用來驗證屬性的人性化顯示名稱以及其餘一些信息
 /// </summary>
 /// <param name="value"></param>
 /// <returns></returns>
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            return ValidationResult.Success;
        }
    }
}

       方法的第一個參數是要驗證的對象的值,若是這個值有效,就能夠返回一個成功的驗證結果。在本例中,在判斷它是否有效時,須要知道欲限制單詞數量的上限,要得到這個上限,可經過建立構造函數要求顧客把最大單詞數做爲一個參數傳遞給它:

public class MaxWordsAttribute : ValidationAttribute
{
    private readonly int _maxWords;
    public MaxWordsAttribute(int maxWords)
    {
        this._maxWords = maxWords;
    }
 
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            var valueString = value.ToString();
            if (valueString.Split(' ').Length > this._maxWords)
            {
                return new ValidationResult("Too many words!");
            }                
        }
        return ValidationResult.Success;
    }
}

       如今已經實現了驗證的邏輯,但上述代碼的問題在於那行返回硬編碼錯誤消息的代碼。使用數據註解的開發人員但願可使用 ValidationAttribute 的 ErrorMessage 屬性來自定義錯誤消息,同時還要與其它驗證特性同樣,提供一個默認的錯誤提示消息(在開發人員沒有提供自定義的錯誤提示消息時使用),而且還要利用驗證的屬性名稱生成錯誤提示消息

public class MaxWordsAttribute : ValidationAttribute
{
    private readonly int _maxWords;
    public MaxWordsAttribute(int maxWords)
        : base("{0} has too many words.") 
        // 設置默認值,若是特性不指明錯誤提示消息的話
        // 若是特性顯式的傳遞了錯誤消息,那麼上面的字符串會被替換
        // 注意,顯式傳遞錯誤消息也是帶有佔位符的,這樣下述語句
        // FormatErrorMessage(validationContext.DisplayName)
        // 纔會把 DisplayName 傳遞至佔位符
    {
        this._maxWords = maxWords;
    }
 
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            var valueString = value.ToString();
            if (valueString.Split(' ').Length > this._maxWords)
            {
                // 基於發生錯誤的數據字段對錯誤消息應用格式設置
                var errorMessage = FormatErrorMessage(validationContext.DisplayName);
                return new ValidationResult(errorMessage);
            }
        }
        return ValidationResult.Success;
    }
}

       代碼做了兩處改動,首先向基類構造函數傳遞了默認的錯誤提示消息,並帶有一個佔位符;調用繼承自基類的 FormatErrorMessage 方法會自動使用 DisplayName 來格式化這個字符串!至此,該特性能夠靈活(靈活即:可指定參數值和錯誤消息)重用。

// 由於在擴展時建立了新的文件夾存放自定義驗證類便於管理
// 所以,使用時須要引用該命名空間
using MvcMusicStore.ExtendValidationAttribute;
 
[Required]
[StringLength(160)]
[MaxWords(10, ErrorMessage = "There are too many words in {0}")]
public string LastName { get; set; }

IValidatableObject

       自驗證(self - validating)模型指一個知道如何驗證自身的模型對象,該模型對象能夠經過實現 IValidatableObject 接口來實現對自身的驗證

       例如,下面在 Order 模型中直接實現對 LastName 字段中單詞個數的檢查:

public partial class Order : IValidatableObject
{
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (LastName != null && LastName.Split(' ').Length > 10)
        {
            yield return new ValidationResult("The last name has too many words!", new[] { "LastName" });
        }
 
        // ......
    }
 
    // rest of Order implementation and properties
    // ...
}

       這種方式與特性版有明顯的不一樣點:

  1. MVC 運行時爲執行驗證而調用的方法名稱是 Validate 而不是 IsValid,更重要的是返回類型和參數也不一樣。
  2. Validate 返回類型是 IEnumerable<ValidationResult>,而不是單獨的 ValidationResult 對象。由於從表面上看,內部的驗證邏輯驗證的是整個模型,所以可能返回多個驗證錯誤。
  3. 沒有 value 參數傳遞給 Validate 方法,由於該方法是一個模型實例方法,所以確定能夠看到當前模型對象自有的屬性值。

       上面的代碼使用了 C# yield return 語法來構建枚舉返回值,同時代碼還須要顯式的告知 ValidationResult 與其關聯的字段名稱,ValidationResult 構造函數最後一個參數是 String 數組,由於這樣可使驗證的結果與多個屬性相關聯(一組屬性都執行這一驗證,返回相同的錯誤提示消息)。

顯示和編輯註解

       和驗證特性同樣,模型元數據提供器會收集下面的顯示(和編輯)註解信息,以供 HTML 輔助方法和 ASP.NET MVC 運行時的其餘組件使用,HTML 輔助方法可使用任何可用的元數據來改變模型的顯示和編輯 UI。

  • Display:爲模型屬性設置友好的「顯示名稱」:[Display(Name = "First Name")];
  • ScaffoldColumn:該特性能夠隱藏 HTML 輔助方法如 EditorForModel 和 DisplayForModel 渲染的一些特性:[ScaffoldColumn(false)]
  • DisplayFormat:經過命名參數來處理屬性的各類格式化選項。下面代碼可格式化爲貨幣:
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:c}")]
public decimal Total { get; set; }
       ApplyFormatInEditMode 參數指示數據字段處於編輯模式時,是否應用屬性指定的格式設置字符串。
  • ReadOnly:確保默認的模型綁定器不使用請求中的新值來更新屬性。如在 TotalPrice(該值爲計算獲得,而不是用戶請求來賦值) 上設置。
  • DataType:這是一個枚舉值,爲運行時提供屬性的特定用途信息。如 string 類型的屬性可應用於不少場合:email、URL、密碼等。
[DataType(DataType.Password)]
[Display(Name = "新密碼")]
public string NewPassword { get; set; }
  • UIHint:給 ASP.NET MVC 運行時提供了一個模版名稱,以備調用模板輔助方法(DisplayFor、EditorFor)渲染輸出時使用。
  • HiddenInput:渲染一個隱藏的文本域,隱藏文本域有時很是好用。(但並不是萬無一失,惡意用戶可篡改提交的表單值)
相關文章
相關標籤/搜索