用4種不同的編程模式驗證綁定參數

ASP.NET MVC採用Model綁定爲目標Action生成了相應的參數列表,但是在真正執行目標Action方法之前,爲了確保有效性,還需要對綁定的參數實施驗證,針對參數的驗證成爲Model綁定。

 

手工驗證綁定的參數

在定義具體Action方法的時候,對已經成功綁定的參數實施手工驗證無疑是一種最爲直接的編程方式,接下來我們通過一個簡單的實例來演示如何將參數驗證邏輯實現在對應的Action方法中,並在沒有通過驗證的情況下將錯誤信息響應給客戶端。我們在一個ASP.NET MVC應用中定義瞭如下一個Person類作爲被驗證的數據類型,它的Name、Gender和Age三個屬性分別表示一個人的姓名、性別和年齡。

public class Person 

   [DisplayName("姓名")] 

   public string Name { get; set; } 

 

   [DisplayName("性別")] 

   public string Gender { get; set; } 

 

   [DisplayName("年齡")] 

   public int? Age { get; set; } 

}

 

接下來我們定義瞭如下一個HomeController。在針對GET請求的Action方法Index中,我們創建了一個Person對象並將其作爲Model呈現在對應的View中。另一個支持POST請求的Index方法具有一個Person類型的參數,我們在該Action方法中先調用Validate方法對這個輸入參數實施驗證。如果驗證成功(ModeState.IsValid屬性返回True),我們返回一個內容爲“輸入數據通過驗證”的ContentResult,否則將此參數作爲Model呈現在對應的View中。

public class HomeController :Controller 

   [HttpGet] 

   public ActionResult Index() 

   { 

       return View(new Person()); 

   } 

 

   [HttpPost] 

   public ActionResult Index(Person person) 

   { 

       Validate(person); 

 

       if (!ModelState.IsValid) 

       { 

           return View(person); 

       } 

       else

       { 

           return Content("輸入數據通過驗證"); 

       } 

   } 

 

   private void Validate(Person person) 

   { 

       if (string.IsNullOrEmpty(person.Name)) 

       { 

           ModelState.AddModelError("Name", "'Name'是必需字段"); 

       } 

 

       if (string.IsNullOrEmpty(person.Gender)) 

       { 

           ModelState.AddModelError("Gender", "'Gender'是必需字段"); 

       } 

       else if (!new string[] { "M", "F" }.Any( 

           g => string.Compare(person.Gender, g, true) == 0)) 

       { 

           ModelState.AddModelError("Gender",  

           "有效'Gender'必須是'M','F'之一"); 

       } 

 

       if (null == person.Age) 

       { 

           ModelState.AddModelError("Age", "'Age'是必需字段"); 

       } 

       else if (person.Age > 25 || person.Age < 18) 

       { 

           ModelState.AddModelError("Age", "有效'Age'必須在18到25週歲之間"); 

       } 

   } 

}

 

如上面的代碼片斷所示,我們在Validate該方法中我們對作爲參數的Person對象的3個屬性進行逐條驗證,如果提供的數據沒有通過驗證,我們會調用當前ModelState的AddModelError方法將指定的驗證錯誤消息轉換爲ModelError保存起來。我們採用的具體的驗證規則如下。

 

Person對象的Name、Gender和Age屬性均爲必需字段,不能爲Null(或者空字符串)。

表示性別的Gender屬性的值必需是“M”(Male)或者“F”(Female),其餘的均爲無效值。

Age屬性表示的年齡必須在18到25週歲之間。

 

如下所示的是Action方法Index對應View的定義,這是一個Model類型爲Person的強類型View,它包含一個用於編輯人員信息的表單。我們直接調用HtmlHelper<TModel> 的擴展方法EditorForModel將作爲Model的Person對象以編輯模式呈現在表單之中。

@model Person 

<html>

<head>

   <title>編輯人員信息</title>

</head>

<body>

   @using (Html.BeginForm()) 

   {  

       @Html.EditorForModel() 

       <input type="submit" value="保存"/>

   } 

</body>

</html>

 

使用ValidationAttribute特性

將針對輸入參數的驗證邏輯和業務邏輯定義在Action方法中並不是一種值得推薦的編程方式。在大部分情況下,同一個數據類型在不同的應用場景中具有相同的驗證規則,如果我們能將驗證規則與數據類型關聯在一起,讓框架本身來實施數據驗證,那麼最終的開發者就可以將關注點更多地放在業務邏輯的實現上面。實際上這也是ASP.NET MVC的Model驗證系統默認支持的編程方式。當我們在定義數據類型的時候,可以在類型及其數據成員上面應用相應的ValidationAttribute特性來定義默認採用的驗證規則。

 

“System.ComponentModel.DataAnnotations”命名空間定義了一系列具體的ValidationAttribute特性類型,它們大都可以直接應用在自定義數據類型的某個屬性上對目標數據成員實施驗證。這些預定義驗證特性不是本章論述的重點,我們會在“下篇”中對它們作一個概括性的介紹。

 

常規驗證可以通過上面列出的這些預定義ValidationAttribute特性來完成,但是在很多情況下我們需要通過創建自定義的ValidationAttribute特性來解決一些特殊的驗證。比如上面演示實例中針對Person對象的驗證中,我們要求Gender屬性指定的表示性別的值必須是“M/m”和“F/f”兩者之一,這樣的驗證就不得不通過自定義的ValidationAttribute特性來實現。

 

針對“某個值必須在指定的範圍內”這樣的驗證規則,我們定義一個DomainAttribute特性。如下面的代碼片斷所示,DomainAttribute具有一個IEnumerable<string>類型的只讀屬性Values提供了一個有效值列表,該列表在構造函數中被初始化。具體的驗證實現在重寫的IsValid方法中,如果被驗證的值在這個列表中,則視爲驗證成功並返回True。爲了提供一個友好的錯誤消息,我們重寫了方法FormatErrorMessage。

[AttributeUsage(AttributeTargets.Property |AttributeTargets.Field,   AllowMultiple =false)] 

public class DomainAttribute :ValidationAttribute 

   public IEnumerable<string> Values { get; private set; } 

 

   public DomainAttribute(string value) 

   { 

       this.Values = new string[] { value }; 

   } 

 

   public DomainAttribute(params string[] values) 

   { 

       this.Values = values; 

   } 

 

   public override bool IsValid(object value) 

   { 

       if (null == value) 

       { 

           return true; 

       } 

       return this.Values.Any(item => value.ToString() == item); 

   } 

 

   public override string FormatErrorMessage(string name) 

   { 

       string[] values = this.Values.Select(value => string.Format("'{0}'",  value)).ToArray(); 

       return string.Format(base.ErrorMessageString,name,string.Join(",",  values)); 

   } 

}

 

由於ASP.NET MVC在進行參數綁定的時候會自動提取應用在目標參數類型或者數據成員上的ValidationAttribute特性,並利用它們對提供的數據實施驗證,所以我們不再需要像上面演示的實例一樣自行在Action方法中實施驗證,而只需要在定義參數類型Person的時候應用相應的ValidationAttribute特性將採用的驗證規則與對應的數據成員相關聯。

 

如下所示的是屬性成員上應用了相關ValidationAttribute特性的Person類型的定義。我們在三個屬性上均應用了RequiredAttribute特性將它們定義成必需的數據成員,Gender和Age屬性上則分別應用了DomainAttribute和RangeAttribute特性對有效屬性值的範圍作了相應限制。

public class Person 

   [DisplayName("姓名")] 

   [Required(ErrorMessageResourceName = "Required",    ErrorMessageResourceType =typeof(Resources))] 

   public string Name { get; set; } 

 

   [DisplayName("性別")] 

   [Required(ErrorMessageResourceName = "Required",   ErrorMessageResourceType =typeof(Resources))] 

   [Domain("M", "F", "m", "f",ErrorMessageResourceName = "Domain", ErrorMessageResourceType = typeof(Resources))] 

   public string Gender { get; set; } 

 

   [DisplayName("年齡")] 

   [Required(ErrorMessageResourceName = "Required",ErrorMessageResourceType = typeof(Resources))] 

   [Range(18, 25, ErrorMessageResourceName = "Range",  ErrorMessageResourceType =typeof(Resources))] 

   public int? Age { get; set; } 

}

 

由於ASP.NET MVC會自動提取應用在綁定參數類型上的ValidationAttribute特性對綁定的參數實施自動化驗證,所以我們根本不需要在具體的Action方法中來對參數作手工驗證。如下面的代碼片斷所示,我們在Action方法Index中不再顯式調用Validate方法,但是運行該程序並在輸入不合法數據的情況下提交表單後依然會得到如圖1所示的輸出結果。

public class HomeController :Controller 

   //其他成員 

   [HttpPost] 

   public ActionResult Index(Person person) 

   { 

       if (!ModelState.IsValid) 

       { 

           return View(person); 

       } 

       else

       { 

           return Content("輸入數據通過驗證"); 

       } 

   } 

}

 

讓數據類型實現IValidatableObject接口

除了將驗證規則通過ValidationAttribute特性直接定義在數據類型上並讓ASP.NET MVC在進行參數綁定過程中據此來驗證參數之外,我們還可以將驗證操作直接定義在數據類型中。既然我們將驗證操作直接實現在了數據類型上,意味着對應的數據對象具有“自我驗證”的能力,我們姑且將這些數據類型稱爲“自我驗證類型”。這些自我驗證類型是實現了具有如下定義的接口IValidatableObject,該接口定義在“System.ComponentModel.DataAnnotations”命名空間下。

 

public interface IValidatableObject 

   IEnumerable<ValidationResult> Validate(  ValidationContext validationContext); 

}

如上面的代碼片斷所示,IValidatableObject接口具有唯一的方法Validate,針對自身的驗證就實現在該方法中。對於上面演示實例中定義的數據類型Person,我們可以按照如下的形式將它定義成自我驗證類型。

 

public class Person:IValidatableObject 

   [DisplayName("姓名")] 

   public string Name { get; set; } 

 

   [DisplayName("性別")] 

   public string Gender { get; set; } 

 

   [DisplayName("年齡")] 

   public int? Age { get; set; } 

 

   public IEnumerable<ValidationResult> Validate( ValidationContextvalidationContext) 

   { 

       Person person = validationContext.ObjectInstance as Person; 

       if (null == person) 

       { 

           yield break; 

       } 

       if(string.IsNullOrEmpty(person.Name)) 

       { 

           yield return new ValidationResult("'Name'是必需字段",new string[]{"Name"}); 

       } 

 

       if (string.IsNullOrEmpty(person.Gender)) 

       { 

           yield return new ValidationResult("'Gender'是必需字段", newstring[] { "Gender" }); 

       } 

       else if (!new string[]{"M","F"}.Any(g=>string.Compare(person.Gender,g, true) == 0)) 

       { 

           yield return new ValidationResult("有效'Gender'必須是'M','F'之一",   new string[] { "Gender" }); 

        } 

 

       if (null == person.Age) 

       { 

           yield return new ValidationResult("'Age'是必需字段",    new string[] { "Age" }); 

       } 

       else if (person.Age > 25 || person.Age < 18) 

       { 

           yield return new ValidationResult("'Age'必須在18到25週歲之間",    new string[] { "Age" }); 

       }             

   } 

}

如上面的代碼片斷所示,我們讓Person類型實現了IValidatableObject接口。在實現的Validate方法中,我們從驗證上下文中獲取被驗證的Person對象,並對其屬性成員進行逐個驗證。如果數據成員沒有通過驗證,我們通過一個ValidationResult對象封裝錯誤消息和數據成員名稱(屬性名),該方法最終返回的是一個元素類型爲ValidationResult的集合。

 

本文爲Anyforweb技術分享博客,需要了解網站建設及更多web應用相關信息,請訪問anyforweb.com。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章