閱讀913 返回首頁    go 汽車大全


在ASP.NET MVC中如何應用多個相同類型的ValidationAttribute?

ASP.NET MVC采用System.ComponentModel.DataAnnotations提供的元數據驗證機製對Model實施驗證,我們可以在Model類型或者字段/屬性上應用相應的ValidationAttribute。但是在默認情況下,對於同一個類型的ValidationAttribute特性隻允許一個應用到目標元素上——即使我們將AllowMultiple屬性設置為True。這篇文章的目的就是為了解決這個問題。[源代碼從這裏下載]

為了演示在相同的目標元素(類、屬性或者字段)應用多個同類的ValidationAttribute,我定義了一個名稱為RangeIfAttribute特性用於進行“有條件的區間驗證”。如下麵的代碼片斷所示,RangeIfAttribute是RangeAttribute的子類,應用在上麵的AttributeUsageAttribute特性的AllowMultiple 屬性被設置為True。RangeIfAttribute定義了Property和Value兩個屬性,分別表示被驗證屬性/字段所在類型的另一個屬性名稱和相應的值,隻有當指定的屬性值與通過Value屬性值相等的情況下我們在真正進行驗證。具體的驗證邏輯定義在重寫的IsValid方法中。

   1: [AttributeUsage( AttributeTargets.Field| AttributeTargets.Property, AllowMultiple = true)]
   2: public class RangeIfAttribute: RangeAttribute
   3: {
   4:     public string Property { get; set; }
   5:     public string Value { get; set; }
   6:     public RangeIfAttribute(string property, string value, double minimum, double maximum)
   7:         : base(minimum, maximum)
   8:     {
   9:         this.Property = property;
  10:         this.Value = value;
  11:     }
  12:     protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  13:     {
  14:         object propertyValue = validationContext.ObjectType.GetProperty(this.Property).GetValue(validationContext.ObjectInstance,null);
  15:         propertyValue = propertyValue ?? "";
  16:         if (propertyValue.ToString()!= this.Value)
  17:         {
  18:             return null;
  19:         }
  20:         if (base.IsValid(value))
  21:         {
  22:             return null;
  23:         }
  24:  
  25:         string[] memberNames = (validationContext.MemberName != null) ? new string[] { validationContext.MemberName } : null;
  26:         return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName), memberNames);
  27:     }
  28: }

我們將RangeIfAttribute特性應在具有如下定義的表示員工的Employee類型的Salary(表示薪水)屬性上,另外一個屬性Grade表示員工的級別。應用在Salary屬性上的RangeIfAttribute特性體現了基於級別的薪水區間驗證規則:對於G7、G8和G9的員工,其薪水分別在2000~3000,3000~4000和4000~5000範圍內。

   1: public class Employee
   2: {
   3:     public string Name { get; set; }
   4:     public string Grad { get; set; }
   5:     [RangeIf("Grad", "G7", 2000, 3000)]
   6:     [RangeIf("Grad", "G8", 3000, 4000)]
   7:     [RangeIf("Grad", "G9", 4000, 5000)]
   8:     public decimal Salary { get; set; }
   9: }

現在我們創建如下一個EmployeeController,其默認的兩個Index操作方法定義如下。在HttpPost的Index操作中,如果驗證成功我們將“驗證成功”字樣作為ModelError添加到ModelState中。

   1: public class EmployeeController : Controller
   2: {
   3:     public ActionResult Index()
   4:     {
   5:         return View(new Employee());
   6:     }
   7:  
   8:     [HttpPost]
   9:     public ActionResult Index(Employee employee)
  10:     {
  11:         if (ModelState.IsValid)
  12:         {
  13:             ModelState.AddModelError("", "驗證成功");
  14:             return View(new Employee());
  15:         }
  16:         else
  17:         {
  18:             return View(new Employee());
  19:         }
  20:     }
  21:  
  22: }

下麵是Index操作默認的View的定義:

   1: @model MultipleValidator.Models.Employee
   2: @{
   3:     ViewBag.Title = "Employee Management";
   4: }
   5: @Html.ValidationSummary(true)
   6: @using (Html.BeginForm())
   7: { 
   8:     @Html.EditorForModel()
   9:     <input type="submit" value="Save" />
  10: }

遺憾的是,ASP.NET MVC並不能按照我們希望的方對我們的輸入進行驗證。如下麵的截圖所示,我們隻有在輸入G9的時候,係統才能實施成功地驗證,對於G7和G8則被輸入的Salary值(0.00)是合法的。

image

之所以會發生上述的這種現象,原因在於被應用到Salary屬性上的RangeIfAttribute特性,最終隻有最後一個(Value=“G9”)被使用到。ASP.NET MVC在生成包括驗證特性的Model的元數據的時候,針對某個元素的所有ValidationAttribute是被維護在一個字典上的,而這個字典的值就是Attribute的TypeId屬性。在默認的情況下,Attribute的TypeId返回的是自身的類型,所以導致應用到相同目標元素的同類ValidationAttribute隻能有一個。幸好Attribute的TypeId屬性是可以被重寫的,縣在我們在RangeIfAttribute中按照如下的方式對這個屬性進行重寫:

   1: [AttributeUsage( AttributeTargets.Field| AttributeTargets.Property, AllowMultiple = true)]
   2: public class RangeIfAttribute: RangeAttribute
   3: {
   4:     //其他成員
   5:     private object typeId;
   6:     public override object TypeId
   7:     {
   8:         get
   9:         {
  10:             return (null == typeId) ? (typeId = new object()) : typeId;
  11:         }
  12:     }
  13: }

再次運行我們的程序則一切正常:

image

值得一提的是:重寫TypeId屬性的方式隻能解決服務端驗證的問題,對於客戶端認證無效。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 14:04:31

  上一篇:go  為什麼System.Attribute的GetHashCode方法需要如此設計?
  下一篇:go  通過擴展改善ASP.NET MVC的驗證機製[實現篇]