ASP.NET MVC4 IN ACTION –Validation
--每個人的都有潛力,不要做被別人看不起的人,You are the best!
本人能力有限,盡量將書中的知識濃縮去講,仔細學過后,然后你再學習其他語言的MVC框架也就大同小異了
本次覆蓋知識點:
- 1. 實現Data Annotations(Implementing Data Annotations )
- 2. 拓展ModelMetadataProvider (Extending the ModelMetadataProvider)
- 3. 使用客戶端驗證 (Enabling client-side validation )
- 4. 創建一個自定義的客戶端驗證器 (Creating custom client side validators )
與讀者的約定:
1.關於rich這個單詞,在本篇有幾處說到一些,為了是博客更好理解,在這里說明一下,以方便本文讀起來更順口。
例如:本波主要講驗證,所以rich experience 豐富的體驗,代表,很復雜的表單,比如qq的注冊頁面,郵箱啊,身份證啊等等,且驗證也很漂亮,我們就說這種體驗叫rich experience,所以rich在本我的博客里面的意思是,用來形容某樣東西,很好的,用起來會很爽的意思。
2. validator:驗證者,在我的博客里,你就理解成,它是一類驗證的工具,比如 email validator驗證email的validator,integer validator驗證數字的validator
3. validation attribute:在服務端驗證的時候我們都會使用在model上的屬性上加上attribute(特性)來約束屬性值的合法性,驗證其合法性,關於validation attribute也就是validation時用到的一些attribute。
4.Unobtrusive JavaScript:簡單地來說,就是一種代碼分離的思想,把行為層和表現層分離開
在前面幾波,我們都在講Model,在本波里面我們還是研究MVC中的M部分,這次我們看一下更復雜的一些環境。在MVC框架里面,提供了一些rich user input validation(用戶輸入后驗證的效果更rich)。Validation很重要,是MVC中的一大特色,為了讓用戶輸入數據在傳入我們的數據庫中,格式符合我們要求,所以我們要加驗證。
在MVC的第一個版本中,框架中是沒有Validation的,它集成了一些第三方驗證框架,很難用,難用體現在,不能擴展,在ASP.NET MVC2中,Validation就已經很完美的支持了,它內置了Microsoft的Data Annotations庫,在MVC3中Validation又有提升,提升了客戶端驗證的環節,已經綽綽有余地滿足了當今的Web應用程序。
幾乎每個應用程序都有登陸頁面的場景,那是很簡單的驗證,過會我們寫一個。在本波博客里,我們主要研究一下在Data Annotations庫中提供的一些內置的validator,接下來,我們拓展model里面的metadata provider,使得用法更好,model中的行為更rich。最后,我們看下客戶端的驗證,因為這是給當今網站訪問者最快的反饋手段,能夠給用戶rich experience,它能更好地滿足當今開發者的需求和用戶的需求。
6.1 服務端驗證(Server-side Validation)
不管客戶端是否驗證了,服務端驗證還是需要的。用戶可以禁用Javascript,也可以通過你想不到的手段繞過驗證,所以服務器端的驗證是防止dirty input(用戶輸入的傳到服務器的臟數據)的最后一道防線了。有些validation rules(規律,規則)要求server端去處理--network topology(網絡位相學)可能是這種情況:只有server可以訪問外部的一些資源(比如js文件)去validate input(驗證用戶輸入的數據)
我們要理解兩個關鍵的思路:使用Data Annotations常用的方式去驗證Server端的驗證;然后我們去investigate(研究)model metadata,再然后我們去寫一個自定義的provider
6.1.1 用Data Annotations方式去Validation
Data Annotations是在.NET 3.5 SP1 介紹到,在System.ComponentModel.DataAnnotations程序集里,是個attributes和類的集合,它允許你使用metadata去修飾你的類。這些metadata描述了一組約定(rules),然后你的object就可以被驗證了。
DataAnnotation的attributes控制了很多的驗證。比如一些新模版的驗證,就像我們在第二波介紹到的DisplayName,DataType特性,這些特性,在表6-1列舉出來了。ASP.NET MVC包括了一個與每個attribute相關的支持驗證的類的集合,讓驗證更出色。
為了證明 validation attribute的使用,讓我們看一下可能用到的驗證,圖6-1是一個Edit 頁面,包含了CompanyName 和 Email Address屬性,由於這些Attribute簡單,所以后面的描述我就不翻譯了。
圖6-1
新建項目ch5_1,無需添加單元測試項目
在Models文件夾中添加CompanyInput.cs
1: using System;
2: using System.Collections.Generic;
3: using System.ComponentModel.DataAnnotations;
4: using System.Linq;
5: using System.Web;
6:
7: namespace Ch5_1.Models
8: {
9: public class CompanyInput
10: {
11: [Required]
12: public string CompanyName { get; set; }
13:
14: [DataType(DataType.EmailAddress)]
15: public string EmailAddress { get; set; }
16: }
17:
18: }
CompanyName使用了RequiredAttribute,EmailAddress使用了DataTypeAttribute(采取了Email地址驗證模版)
在view中,我們需要展示驗證失敗的錯誤信息,我們有很多種方法完成,如果我們使用model模版,驗證的信息就已經在模版中了。
打開HomeController添加一個Edit action
1: public ActionResult Edit()
2: {
3: return View();
4: }
並添加對應的視圖,Edit.cshtml
默認的editor model模版會生成user interface(一個又一個input節點和驗證信息)
為了更好地控制output,我們可以使用HtmlHelper驗證的拓展方法,ValidationSummary拓展提供了一個驗證錯誤的信息的總結列表,通常展示在form的上面,為了是特定的驗證的錯誤信息對應指定的model屬性,我們可以使用ValidationMessage或者基於表達式的ValidationMessageFor方法。
在寫好validation message信息,一切准備就緒之后,我們需要在我們的controller中的action中審核我們的model的Valid屬性
我們使用ModelState.IsValid判斷是否驗證通過,通過后,MVC驗證引擎會把validation errors放進ModelState里面,然后把錯誤的信息的是否存在放在IsValid屬性里,如果沒有錯誤,我們將會跳轉到Success view頁面,否則,會顯示錯誤。
按下F5運行項目,直接點擊submit
在圖6-2中,在驗證的錯誤的信息上還有一些問題,”CompanyName”中間沒有空格,我們希望有個空格,一種是我們修復label,包括DisplayNameAttribute(在System.Component空間中),但是因為在單詞之間添加一個空格是有必要的,下面我們拓展ModelMetadataProvider類讓它自動有空格
======本波博客來自茗洋芳竹所有,任何人未經允許不得轉載:http://www.cnblogs.com/AaronYang======
6.1.2 拓展ModelMetadataProvider
在前面幾節我們都看到了ASP.NET MVC中的model metadata,使用model metadata的展示的input element和文本,validation providers使用model metadata去執行驗證的模版
如果我們想要我們的model metadata(元數據)從sources賦值,而不是Data Annotations的時候,我們就需要從ModelMetadataProvider開始下手。
ModelMetadataProvider 類包含了一些方法:在類型中的每一個成員的ModelMetadata的集合,能夠指定屬性的獲得ModelMetadata的方法,能夠根據指定類型獲得ModelMetadata,所有的這些可以在列表6-1看到
為了能夠達到自定義指定屬性的展示的文本的形式,我們需要重寫DataAnnotationsModelMetadataProvider類
在我們的例子里,我們需要修改DisplayName model metadata的行為,默認的ModelMetadata的DisplayName屬性來自DisplayNameAttribute。我們仍然通過Attribute來補充DisplayName值
在下面代碼中,我們拓展了內置的DataAnnotationsModelMetadataProvider 來,通過屬性名稱來分離成單詞的方式來創造 DisplayName最終顯示的樣子
我們在Models中創建一個繼承DataAnnotationsModelMetadataProvider的ConventionProvider.cs文件
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text.RegularExpressions;
5: using System.Web;
6: using System.Web.Mvc;
7:
8: namespace Ch5_1.Models
9: {
10: public class ConventionProvider : DataAnnotationsModelMetadataProvider
11: {
12: protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
13: {
14: var meta = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
15: if (meta.DisplayName == null)
16: {
17: meta.DisplayName = meta.PropertyName.ToSeparatedWords();
18: }
19: return meta;
20: }
21: }
22: public static class StringExtensions
23: {
24: public static string ToSeparatedWords(this string value)
25: {
26: if (value != null)
27: return Regex.Replace(value, "([A-Z][a-z]?)", " $1").Trim();
28: return value;
29: }
30: }
31: }
ToSeparatedword是一個根據Pascal命名法分隔單詞,例如CompanyNameWa會變成 Company Name Wa的形式
當我們自定義的ModelMetadataProvider構建好后,我們需要讓我們的ASP.NET MVC框架去使用我們的新provider。在哪里初始化聲明呢?這最常見的位置就是Global.asax文件中了
按下F5運行,點提交,查看錯誤狀態,Display模版都有空格了,按照Pascal方式斷詞了
到目前位置,這個例子中,都是服務器端的驗證,但是ASP.NET MVC提供了分離的客戶端和服務器端驗證的支持,接下來我們看看客戶端的驗證
6.2 客戶端驗證(Client-side validation)
隨着更多時髦的瀏覽器,還有rich Client的到來,在form中,使用Javascript去驗證已經成為主要手段。客戶端驗證的反饋比服務端的反饋更快,因為服務端驗證,從客戶到服務端的一次輪詢是不可避免的,許多客戶端驗證框架包括了高級的功能:比如當input element失去焦點的時候開始驗證,所以用戶在tapping(敲擊鍵盤)的時候,當離開表單的時候就會獲得動態的驗證信息。
西拼亂湊綁定驗證行為是浪費時間和成本過高的。因為許多客戶端的驗證框架在開發的時候,還有產品發行這幾年已經完成了,基本完美了。所以沒必要寫重復的代碼了,在ASP.NET MVC中,這種重復的勞動已經大大的減少了。
ASP.NET MVC捆綁了用於客戶端驗證的Jquery Validate庫的支持,另一個新的特色是支持unobtrusive客戶端驗證,這是一種在呈現的input element上使用data特性的一種腳本引用,驗證腳本會監測這個節點,並相應的給出反應,在ASP.NET MVC2中,客戶端驗證是不突出的,也就是一段特殊的腳本跟着input節點,然后與input建立關聯
在下一節中,我們將要研究在ASP.NET MVC中的客戶端驗證,建立一個基本的項目,研究自定義規則的兩個點—RemoteAttribute,創建一個自定義的JQuery validators
6.2.1 開始客戶端驗證
開始學習客戶端驗證,首先在shared文件下的_Layout.cshtml文件中添加腳本。
1: <script src="@Url.Content("~/Scripts/jquery-1.8.2.min.js")"
2: type="text/javascript"></script>
3: <script src="@Url.Content("~/Scripts/jquery.validate.js")"
4: type="text/javascript"></script>
5: <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"
6: type="text/javascript"></script>
每一個Javascript庫都是建立在上一個Javascript基礎上的,所以這3個js文件的放置順序很重要,我們首先注冊Jquery庫,不久注冊JQuery插件,然后就是unobtrusive驗證。
把這些客戶端庫包含在母版頁里,我們就可以有選擇地進行 unobtrusive客戶端驗證了。這一點可以在應用程序級別的web.config中完成,或者在每一個請求的頁面上加上兩個方法。
關於那兩個方法必須放在BeginForm前面,如上那兩段代碼表示這兩個腳本啟用,然后就可以在頁面上這樣寫代碼了,就可以自動驗證
這些metadata來自jquery.unobtrusive JavaScript庫,還有JQuery Validate 插件驗證的邏輯
由於我們服務端的驗證還在,我們完全放下心來,即使瀏覽器禁止掉Javascript,也還是能啟動驗證的邏輯的,ASP.NET MVC也支持自定義的validator,一個插件,為客戶端和服務器端准備的。
======本波博客來自茗洋芳竹所有,任何人未經允許不得轉載:http://www.cnblogs.com/AaronYang======
6.2.2 RemoteAttribute的使用
在ASP.NET MVC3中有了一個新的validation特性就是RemoteAttribute。修飾了一個model屬性,通知Jquery驗證發送一個Http請求給一個action方法做一次服務器端驗證,然后處理結果會返回給客戶端,然后一個錯誤信息就會在表單提交之前顯示。這是個很好的方式,提供了一個rich client experience(富客戶端體驗)的很好的一個服務器端驗證的方法。
1: using System;
2: using System.Collections.Generic;
3: using System.ComponentModel.DataAnnotations;
4: using System.Linq;
5: using System.Web;
6: using System.Web.Mvc;
7:
8: namespace Ch5_1.Models
9: {
10: public class UsingRemote
11: {
12: [Required]
13: [Remote("IsNumberEven", "Home", ErrorMessage = "數字必須是偶數!")]
14: public int EvenNumber { get; set; }
15: }
16: }
這個特性表明了哪一個controller,哪一個action,客戶端腳本將會調用,它也指定了一個錯誤的信息,在用戶改變節點(element)值的時候,客戶端腳本將會發送name和value給action,這個action名字參數必須和input element的名字一致,匹配
1: public JsonResult IsNumberEven(int evenNumber)
2: {
3: return Json(evenNumber % 2 == 0,
4: JsonRequestBehavior.AllowGet);
5: }
這個action的作用是,驗證這個數字是不是偶數,並且返回一個Boolean型的,然后被Json方法包裹了,這個Boolean決定了是否成功,true當然就是意味着驗證成功。在這種情況下,當用戶在填寫form的時候,用戶要承受一點點HTTP請求,RemoteAttribute是一個很簡單就能豐富客戶端體驗的一個很好的方式,如果你更關心性能,很多這些請求都可以轉換成一個自定義的客戶端驗證。
開始練習:
我們首先在Home控制器中,添一個action
1: public ActionResult RemoteAttribute(UsingRemote input)
2: {
3: return View(input);
4: }
添加對應的視圖:
1: @model Ch5_1.Models.UsingRemote
2:
3: @{
4: ViewBag.Title = "Using RemoteAttribute";
5: }
6:
7: <h2>Edit</h2>
8:
9: @using (Html.BeginForm("IsNumberEven", "Home")) {
10:
11: @Html.EditorForModel()
12: <button type="submit">Submit</button>
13: }
運行效果圖如下:
所以Remote特性是一個,特殊單個屬性可以使用特定的controller action去處理這個單個屬性.
======本波博客來自茗洋芳竹所有,任何人未經允許不得轉載:http://www.cnblogs.com/AaronYang======
6.2.3 創建一個自定義的客戶端Validator
當一個驗證中的特性實現了IClientValidatable,DataAnnotationsModelMetadataProvider(還有很多的衍生,比如ConventionProvider),它就可以通知框架把這些數據特性和HTMl中的element(DOM節點)關聯起來。使用這個機制,我們可以實現自定義的客戶端驗證,使用我們自己的JavaScript代碼去驗證,當JQuery驗證庫提供的validator不夠用時候,這個是對特定的應用程序行為是有用的。
在下面一個例子里,我將要添加一個驗證邏輯去確保一個表單上的時間是晚於其他的時間的。用戶不想在一個錯誤的需求上輸入日期,我們給他們在提交表單之前提供一個更快的選擇的方式,IClientValidatable接口有一個方法,它提供了自定義validator的metadata
1: public interface IClientValidatable
2: {
3: IEnumerable<ModelClientValidationRule> GetClientValidationRules(
4: ModelMetadata metadata, ControllerContext context);
5: }
最終效果圖:結束時間要小於開始時間
這個方法接受了一個model metadata參數,所以在我們驗證的時候,我們可以為我們的指定的model屬性自定義一個驗證規則,在我們的例子中,我們使用格式化后Displayname(比如原來是CompanyName我們格式化后是Company Name)去生成一個錯誤的信息,為了確保最大的拓展性,這個方法返回了一個ModelClientValidationRule泛型的IEnumerable集合,但是它返回了一個約束的集合還是好的,在我們遇到所有的例子中,一個一個關聯都是有意義的。IClientValidatable 接口允許 ASP.NET MVC 在運行時發現支持的客戶端驗證器,這個接口被用來支持集成不同的驗證框架.
實現的代碼如下:
①在Models文件夾新建一個DateComesLaterAttribute.cs類,讓它繼承ValidationAttribute,實現我們自己的名稱的特性,實現后,重寫IsValid方法,這里的屬性過會就是特性里面的參數了
1: public class DateComesLaterAttribute : ValidationAttribute
2: {
3: public const string DefaultErrorMessage = "'{0}' must be after '{1}'";
4:
5: protected readonly string OtherDateProperty;
6: /// <summary>
7: /// 構造函數
8: /// </summary>
9: /// <param name="otherDateProperty">其他日期屬性</param>
10: public DateComesLaterAttribute(string otherDateProperty)
11: {
12: this.OtherDateProperty = otherDateProperty;
13: }
14:
15: protected override ValidationResult IsValid(object value, ValidationContext validationContext)
16: {
17: object instance = validationContext.ObjectInstance;
18: Type type = validationContext.ObjectType;
19:
20: var earlierDate = (DateTime?)type.GetProperty(OtherDateProperty).GetValue(instance, null);
21: var date = (DateTime?)value;
22:
23: if (date > earlierDate)
24: return ValidationResult.Success;
25:
26: string errorMessage = GetErrorMessage(validationContext.ObjectType, validationContext.DisplayName);
27:
28: return new ValidationResult(errorMessage);
29: }
30:
31: protected string GetErrorMessage(Type containerType, string displayName)
32: {
33: ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, containerType,
34: OtherDateProperty);
35: var otherDisplayName = metadata.GetDisplayName();
36: return ErrorMessage ?? string.Format(DefaultErrorMessage, displayName, otherDisplayName);
37: }
為了讓DateComesLaterAttribute 驗證屬性也能在客戶端進行驗證,我們索性就再寫一個特性,實現IClientValidatable接口
1: public class DateComesLaterClientAttribute : DateComesLaterAttribute, IClientValidatable
2: {
3: public DateComesLaterClientAttribute(string otherDateProperty) : base(otherDateProperty) { }
4:
5: public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
6: ControllerContext context)
7: {
8: var rule = new ModelClientValidationRule
9: {
10: ErrorMessage = GetErrorMessage(metadata.ContainerType, metadata.GetDisplayName()),
11: ValidationType = "later",
12: };
13:
14: rule.ValidationParameters.Add("other", "*." + OtherDateProperty);
15:
16: yield return rule;
17: }
18: }
IClientValidatable 接口很簡單,他只有一個GetClientValidationRules()方法,我們只要實現這個方法就可以了,這個方法包括兩個參數,metadata表示要驗證的屬性的元數據,context表示發送請求的Controller的上下文,並且它返回一個ModelClientValidationRule集合。
ModelClientValidationRule 是一個只有3個屬性的很簡單的一個類.驗證失敗后,ErrorMessage就會顯示出來,ValidateType是validator的名字,ValidationParameters是一個IDictionary<string,object>-一組可以通過客戶端腳本驗證的參數.在上面代碼中,我們設置的錯誤信息是基於displayname的.這里ValidateType是"later",這個是我們過會將要寫的JQuery Validator的名字.我們還要添加一個其他的數據屬性,就是我們將要跟它比較的,參考的一個對象.
實現了IClientValidatable后,ASP.NET MVC就會使用JQuery Validate為unobtrusive client validation呈現正確的特性,兩個步驟:①寫一個JQuery Validate validator ②使用unobtrusive特性和validator關聯起來
我們過會將使用一個很簡短的JavaScript代碼來添加一個validator,這個validator的作用就是保證它的日期比別的日期element都新,我們使用JavaScript的Date對象來做比較.value參數是我們將要驗證的值,params參數是其他input element(我們在驗證特性中定義的)
我們打開Scripts文件夾下的jquery.validate.unobtrusive.js文件
然后我們,繼續往下面查看代碼,會發現:
接下來,我們在這塊添加如下代碼(可添可不添加)
1: adapters.add("later", ["other"], function (options) {
2: var prefix = getModelPrefix(options.element.name),
3: other = options.params.other,
4: fullOtherName = appendModelPrefix(other, prefix),
5: element = $(options.form).find(":input[name=" + fullOtherName + "]")[0];
6: setValidationValues(options, "later", element);
7: });
添加這行代碼,我們就可以在客戶端的用unobstrusive JavaScript方式驗證,我們先采用server端方式
接下來我們在Models文件夾中新建一個ClientCustomValidation.cs
1: using System;
2: using System.Collections.Generic;
3: using System.ComponentModel.DataAnnotations;
4: using System.Linq;
5: using System.Web;
6:
7: namespace Ch5_1.Models
8: {
9: public class ClientCustomValidation
10: {
11: [DataType(DataType.Date)]
12: [Required]
13: public DateTime? BeginDate { get; set; }
14:
15: [DataType(DataType.Date)]
16: [DateComesLaterClient("BeginDate")]
17: public DateTime? EndDate { get; set; }
18: }
19: }
在Home控制器中,添加一個action,和對應的view
1: public ActionResult ClientCustomValidation() {
2:
3: return View(new ClientCustomValidation());
4: }
5:
6: [HttpPost]
7: public ActionResult ClientCustomValidation(ClientCustomValidation input)
8: {
9: if (ModelState.IsValid)
10: {
11: return View("Success");
12: }
13: return View(new ClientCustomValidation());
14: }
view中:
1: @model Ch5_1.Models.ClientCustomValidation
2:
3: @{
4: ViewBag.Title = "ClientCustomValidation";
5: }
6:
7: <h2>驗證日期Demo</h2>
8:
9: @using (Html.BeginForm("ClientCustomValidation", "Home"))
10: {
11: @Html.EditorForModel()
12: <button type="submit">Submit</button>
13: }
效果圖:
關於 client端的這種驗證,我沒有去做,這個例子,只是說明如何自定義一個 驗證特性,然后使用它.
書上講的 省略死我了,簡直在虐我,我東查資料,西查資料完成的.
看我這么辛苦的份上,給個推薦吧,讓大家知道我這個ASP.NET MVC4 IN ACTION系列文章,讓更多的人可以更快的入門MVC
謝謝你們的支持!
關於下一波博客,我打算分兩波的博客講解,MVC中的AJAX方面的,內容很多,估計是本波博客的2倍的量,好有壓力~~o(>_<)o ~~
關於ASP.NET MVC4 IN ACTION系列目錄地址已經生成:點擊查看目錄
本波博客代碼: 點擊下載