ASP.NET MVC下的四種驗(yàn)證編程方式
ASP.NET MVC采用Model綁定為目標(biāo)Action生成了相應(yīng)的參數(shù)列表,但是在真正執(zhí)行目標(biāo)Action方法之前,還需要對(duì)綁定的參數(shù)實(shí)施驗(yàn)證以確保其有效性,我們將針對(duì)參數(shù)的驗(yàn)證成為Model綁定。總地來說,我們可以采用4種不同的編程模式來進(jìn)行針對(duì)綁定參數(shù)的驗(yàn)證。
一、手工驗(yàn)證綁定的參數(shù)
在定義具體Action方法的時(shí)候,對(duì)已經(jīng)成功綁定的參數(shù)實(shí)施手工驗(yàn)證無疑是一種最為直接的編程方式,接下來我們通過一個(gè)簡(jiǎn)單的實(shí)例來演示如何將參數(shù)驗(yàn)證邏輯實(shí)現(xiàn)在對(duì)應(yīng)的Action方法中,并在沒有通過驗(yàn)證的情況下將錯(cuò)誤信息響應(yīng)給客戶端。我們?cè)谝粋€(gè)ASP.NET MVC應(yīng)用中定義了如下一個(gè)Person類作為被驗(yàn)證的數(shù)據(jù)類型,它的Name、Gender和Age三個(gè)屬性分別表示一個(gè)人的姓名、性別和年齡。
- public class Person
- {
- [DisplayName("姓名")]
- public string Name { get; set; }
- [DisplayName("性別")]
- public string Gender { get; set; }
- [DisplayName("年齡")]
- public int? Age { get; set; }
- }
接下來我們定義了如下一個(gè)HomeController。在針對(duì)GET請(qǐng)求的Action方法Index中,我們創(chuàng)建了一個(gè)Person對(duì)象并將其作為Model呈現(xiàn)在對(duì)應(yīng)的View中。另一個(gè)支持POST請(qǐng)求的Index方法具有一個(gè)Person類型的參數(shù),我們?cè)谠揂ction方法中先調(diào)用Validate方法對(duì)這個(gè)輸入?yún)?shù)實(shí)施驗(yàn)證。如果驗(yàn)證成功(ModeState.IsValid屬性返回True),我們返回一個(gè)內(nèi)容為“輸入數(shù)據(jù)通過驗(yàn)證”的ContentResult,否則將此參數(shù)作為Model呈現(xiàn)在對(duì)應(yīng)的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("輸入數(shù)據(jù)通過驗(yàn)證");
- }
- }
- 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周歲之間");
- }
- }
- }
如上面的代碼片斷所示,我們?cè)赩alidate該方法中我們對(duì)作為參數(shù)的Person對(duì)象的3個(gè)屬性進(jìn)行逐條驗(yàn)證,如果提供的數(shù)據(jù)沒有通過驗(yàn)證,我們會(huì)調(diào)用當(dāng)前ModelState的AddModelError方法將指定的驗(yàn)證錯(cuò)誤消息轉(zhuǎn)換為ModelError保存起來。我們采用的具體的驗(yàn)證規(guī)則如下。
- Person對(duì)象的Name、Gender和Age屬性均為必需字段,不能為Null(或者空字符串)。
- 表示性別的Gender屬性的值必需是“M”(Male)或者“F”(Female),其余的均為無效值。
- Age屬性表示的年齡必須在18到25周歲之間。
如下所示的是Action方法Index對(duì)應(yīng)View的定義,這是一個(gè)Model類型為Person的強(qiáng)類型View,它包含一個(gè)用于編輯人員信息的表單。我們直接調(diào)用HtmlHelper<TModel> 的擴(kuò)展方法EditorForModel將作為Model的Person對(duì)象以編輯模式呈現(xiàn)在表單之中。
- @model Person
- <html>
- <head>
- <title>編輯人員信息</title>
- </head>
- <body>
- @using (Html.BeginForm())
- {
- @Html.EditorForModel()
- <input type="submit" value="保存"/>
- }
- </body>
- </html>
直接運(yùn)行該程序后,一個(gè)用于編輯人員基本信息的頁面會(huì)被呈現(xiàn)出來,如果我們?cè)谳斎氩缓戏ǖ臄?shù)據(jù)并提交后,相應(yīng)的驗(yàn)證信息會(huì)以圖1所示的形式呈現(xiàn)出來。
二、使用ValidationAttribute特性
將針對(duì)輸入?yún)?shù)的驗(yàn)證邏輯和業(yè)務(wù)邏輯定義在Action方法中并不是一種值得推薦的編程方式。在大部分情況下,同一個(gè)數(shù)據(jù)類型在不同的應(yīng)用場(chǎng)景中具有相同的驗(yàn)證規(guī)則,如果我們能將驗(yàn)證規(guī)則與數(shù)據(jù)類型關(guān)聯(lián)在一起,讓框架本身來實(shí)施數(shù)據(jù)驗(yàn)證,那么最終的開發(fā)者就可以將關(guān)注點(diǎn)更多地放在業(yè)務(wù)邏輯的實(shí)現(xiàn)上面。實(shí)際上這也是ASP.NET MVC的Model驗(yàn)證系統(tǒng)默認(rèn)支持的編程方式。當(dāng)我們?cè)诙x數(shù)據(jù)類型的時(shí)候,可以在類型及其數(shù)據(jù)成員上面應(yīng)用相應(yīng)的ValidationAttribute特性來定義默認(rèn)采用的驗(yàn)證規(guī)則。
“System.ComponentModel.DataAnnotations”命名空間定義了一系列具體的ValidationAttribute特性類型,它們大都可以直接應(yīng)用在自定義數(shù)據(jù)類型的某個(gè)屬性上對(duì)目標(biāo)數(shù)據(jù)成員實(shí)施驗(yàn)證。這些預(yù)定義驗(yàn)證特性不是本章論述的重點(diǎn),我們會(huì)在“下篇”中對(duì)它們作一個(gè)概括性的介紹。
常規(guī)驗(yàn)證可以通過上面列出的這些預(yù)定義ValidationAttribute特性來完成,但是在很多情況下我們需要通過創(chuàng)建自定義的ValidationAttribute特性來解決一些特殊的驗(yàn)證。比如上面演示實(shí)例中針對(duì)Person對(duì)象的驗(yàn)證中,我們要求Gender屬性指定的表示性別的值必須是“M/m”和“F/f”兩者之一,這樣的驗(yàn)證就不得不通過自定義的ValidationAttribute特性來實(shí)現(xiàn)。
針對(duì) “某個(gè)值必須在指定的范圍內(nèi)”這樣的驗(yàn)證規(guī)則,我們定義一個(gè)DomainAttribute特性。如下面的代碼片斷所示,DomainAttribute具有一個(gè)IEnumerable<string>類型的只讀屬性Values提供了一個(gè)有效值列表,該列表在構(gòu)造函數(shù)中被初始化。具體的驗(yàn)證實(shí)現(xiàn)在重寫的IsValid方法中,如果被驗(yàn)證的值在這個(gè)列表中,則視為驗(yàn)證成功并返回True。為了提供一個(gè)友好的錯(cuò)誤消息,我們重寫了方法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在進(jìn)行參數(shù)綁定的時(shí)候會(huì)自動(dòng)提取應(yīng)用在目標(biāo)參數(shù)類型或者數(shù)據(jù)成員上的ValidationAttribute特性,并利用它們對(duì)提供的數(shù)據(jù)實(shí)施驗(yàn)證,所以我們不再需要像上面演示的實(shí)例一樣自行在Action方法中實(shí)施驗(yàn)證,而只需要在定義參數(shù)類型Person的時(shí)候應(yīng)用相應(yīng)的ValidationAttribute特性將采用的驗(yàn)證規(guī)則與對(duì)應(yīng)的數(shù)據(jù)成員相關(guān)聯(lián)。
如下所示的是屬性成員上應(yīng)用了相關(guān)ValidationAttribute特性的Person類型的定義。我們?cè)谌齻€(gè)屬性上均應(yīng)用了RequiredAttribute特性將它們定義成必需的數(shù)據(jù)成員,Gender和Age屬性上則分別應(yīng)用了DomainAttribute和RangeAttribute特性對(duì)有效屬性值的范圍作了相應(yīng)限制。
- 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; }
- }
三個(gè)ValidationAttribute特性采用的錯(cuò)誤消息均定義在項(xiàng)目默認(rèn)的資源文件中(我們可以采用這樣的步驟創(chuàng)建這個(gè)資源文件:右鍵選擇Solution Exploror中的項(xiàng)目,并在上下文菜單中選擇“屬性”選項(xiàng)打開“項(xiàng)目屬性”對(duì)象框。最后在對(duì)話框中選擇“資源”Tab頁面,通過點(diǎn)擊頁面中的鏈接創(chuàng)建一個(gè)資源文件),具體定義如圖2所示。
由于ASP.NET MVC會(huì)自動(dòng)提取應(yīng)用在綁定參數(shù)類型上的ValidationAttribute特性對(duì)綁定的參數(shù)實(shí)施自動(dòng)化驗(yàn)證,所以我們根本不需要在具體的Action方法中來對(duì)參數(shù)作手工驗(yàn)證。如下面的代碼片斷所示,我們?cè)贏ction方法Index中不再顯式調(diào)用Validate方法,但是運(yùn)行該程序并在輸入不合法數(shù)據(jù)的情況下提交表單后依然會(huì)得到如圖1所示的輸出結(jié)果。
- public class HomeController : Controller
- {
- //其他成員
- [HttpPost]
- public ActionResult Index(Person person)
- {
- if (!ModelState.IsValid)
- {
- return View(person);
- }
- else
- {
- return Content("輸入數(shù)據(jù)通過驗(yàn)證");
- }
- }
- }
#p#
三、讓數(shù)據(jù)類型實(shí)現(xiàn)IValidatableObject接口
除了將驗(yàn)證規(guī)則通過ValidationAttribute特性直接定義在數(shù)據(jù)類型上并讓ASP.NET MVC在進(jìn)行參數(shù)綁定過程中據(jù)此來驗(yàn)證參數(shù)之外,我們還可以將驗(yàn)證操作直接定義在數(shù)據(jù)類型中。既然我們將驗(yàn)證操作直接實(shí)現(xiàn)在了數(shù)據(jù)類型上,意味著對(duì)應(yīng)的數(shù)據(jù)對(duì)象具有“自我驗(yàn)證”的能力,我們姑且將這些數(shù)據(jù)類型稱為“自我驗(yàn)證類型”。這些自我驗(yàn)證類型是實(shí)現(xiàn)了具有如下定義的接口IValidatableObject,該接口定義在“System.ComponentModel.DataAnnotations”命名空間下。
- public interface IValidatableObject
- {
- IEnumerable<ValidationResult> Validate( ValidationContext validationContext);
- }
如上面的代碼片斷所示,IValidatableObject接口具有唯一的方法Validate,針對(duì)自身的驗(yàn)證就實(shí)現(xiàn)在該方法中。對(duì)于上面演示實(shí)例中定義的數(shù)據(jù)類型Person,我們可以按照如下的形式將它定義成自我驗(yàn)證類型。
- 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( ValidationContext validationContext)
- {
- 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'是必需字段", new string[] { "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類型實(shí)現(xiàn)了IValidatableObject接口。在實(shí)現(xiàn)的Validate方法中,我們從驗(yàn)證上下文中獲取被驗(yàn)證的Person對(duì)象,并對(duì)其屬性成員進(jìn)行逐個(gè)驗(yàn)證。如果數(shù)據(jù)成員沒有通過驗(yàn)證,我們通過一個(gè)ValidationResult對(duì)象封裝錯(cuò)誤消息和數(shù)據(jù)成員名稱(屬性名),該方法最終返回的是一個(gè)元素類型為ValidationResult的集合。在不對(duì)其他代碼作任何改動(dòng)的情況下,我們直接運(yùn)行該程序并在輸入不合法數(shù)據(jù)的情況下提交表單后依然會(huì)得到如圖1所示的輸出結(jié)果。
四、讓數(shù)據(jù)類型實(shí)現(xiàn)IDataErrorInfo接口
上面我們讓數(shù)據(jù)類型實(shí)現(xiàn)IValidatableObject接口并將具體的驗(yàn)證邏輯定義在實(shí)現(xiàn)的Validate方法中,這樣的類型能夠被ASP.NET MVC所識(shí)別,后者會(huì)自動(dòng)調(diào)用該方法對(duì)綁定的數(shù)據(jù)對(duì)象實(shí)施驗(yàn)證。如果我們讓數(shù)據(jù)類型實(shí)現(xiàn)IDataErrorInfo接口也能實(shí)現(xiàn)類似的自動(dòng)化驗(yàn)證效果。
IDataErrorInfo接口定義在“System.ComponentModel”命名空間下,它提供了一種標(biāo)準(zhǔn)的錯(cuò)誤信息定制方式。如下面的代碼片段所示,IDataErrorInfo具有兩個(gè)成員,只讀屬性Error用于獲取基于自身的錯(cuò)誤消息,而只讀索引用于返回指定數(shù)據(jù)成員的錯(cuò)誤消息。
- public interface IDataErrorInfo
- {
- string Error { get; }
- string this[string columnName] { get; }
- }
同樣是針對(duì)上面演示的實(shí)例,現(xiàn)在我們對(duì)需要被驗(yàn)證的數(shù)據(jù)類型Person進(jìn)行了重新定義。如下面的代碼片斷所示,我們讓Person實(shí)現(xiàn)了IDataErrorInfo接口。在實(shí)現(xiàn)的索引中,我們將索引參數(shù)columnName視為屬性名稱,根據(jù)它按照上面的規(guī)則對(duì)相應(yīng)的屬性成員實(shí)施驗(yàn)證,并在驗(yàn)證失敗的情況下返回相應(yīng)的錯(cuò)誤消息。在不對(duì)其他代碼作任何改動(dòng)的情況下,我們直接運(yùn)行該程序并在輸入不合法數(shù)據(jù)的情況下提交表單后依然會(huì)得到如圖1所示的輸出結(jié)果。
- public class Person : IDataErrorInfo
- {
- [DisplayName("姓名")]
- public string Name { get; set; }
- [DisplayName("性別")]
- public string Gender { get; set; }
- [DisplayName("年齡")]
- public int? Age { get; set; }
- [ScaffoldColumn(false)]
- public string Error { get; private set; }
- public string this[string columnName]
- {
- get
- {
- switch (columnName)
- {
- case "Name":
- {
- if(string.IsNullOrEmpty(this.Name))
- {
- return "'姓名'是必需字段";
- }
- return null;
- }
- case "Gender":
- {
- if (string.IsNullOrEmpty(this.Gender))
- {
- return "'性別'是必需字段";
- }
- else if (!new string[] { "M", "F" }.Any(
- g => string.Compare(this.Gender, g, true) == 0))
- {
- return "'性別'必須是'M','F'之一";
- }
- return null;
- }
- case "Age":
- {
- if (null == this.Age)
- {
- return "'年齡'是必需字段";
- }
- else if (this.Age > 25 || this.Age < 18)
- {
- return "'年齡'必須在18到25周歲之間";
- }
- return null;
- }
- default: return null;
- }
- }
- }
- }
原文鏈接:http://www.cnblogs.com/artech/p/asp-net-mvc-validation-programming.html