ASP.NET MVC3 自訂驗證規則 是否為電子郵件阿?(使用 AddBool 方法)
- 2011-07-08
- 20910
- 0
- ASP.NET MVC3 驗證介紹實作與擴充
- 版本
- 3
在上一篇系列文中介紹了 ASP.NET MVC3 原生的驗證使用方法,是很好用但是稍嫌不足,如果今天我們要驗證欄位的值是否為 Email 的格式,不可能每次都組出 Regex 來驗證,這是會累死人的,而且也不能保證每次寫出來的 Regex 都一樣,所以自訂驗證規則就是很重要的了,在 ASP.NET MVC3 中可以輕鬆自在的實作 ValidationAttribute 和 IClientValidatable 來擴充需要的驗證邏輯,此篇文章就先來介紹最簡單的 是否驗證。
是否為 電子郵件? 是否是身分證字號? 是否?是否?是否!? 網頁實作中經常性的需要驗證這種有固定格式的值,在上一篇的介紹中,您可能會利用 RegularExpression 來處理這種事情
[Required] [DataType(DataType.EmailAddress)] [Display(Name = "Email")] [RegularExpression(@"\w.+\@\w.+")] public string Email { get; set; }
使用 RegularExpression 的確是能解決眼下的問題,但可能會引發更多的問題依據上方的 Code 只是檢查字串中有沒有 @ 符號,是稍嫌不夠的所以我們會寫出更複雜的 Regex pattern
[Required] [DataType(DataType.EmailAddress)] [Display(Name = "Email")] [RegularExpression(@"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i",ErrorMessage="Email錯誤")] public string Email { get; set; }
以上 Regex pattern 是使用 jQuery.Validate 內的 Email 驗證
看看上面的 Regex 是什麼玩意....你有辦法保證第二次的會一模一樣嗎,就算是複製貼上都可能少複製了一點,因此雖然 RegularExpression 可以處理這種事情,但絕對不推薦使用這種方法,所以本文的主角出現了,自訂驗證邏輯,本篇先介紹最簡單也最常使用的 bool 這種驗證沒有傳入參數只可以傳入要驗證的值,因此很適合拿來驗證 Email 格式、身分證字號、統一編號這種格式固定的規則,現在馬上就來自訂一個 Email 格式檢查的驗證規則吧。
為了方便分類建立一個 ValidateAttribute 資料夾,並且新增一個 EmailAttribute 類別
開啟 EmailAttribute 輸入以下的 Code
public sealed class EmailAttribute : ValidationAttribute { public const string RegExString = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$"; public EmailAttribute() { } public override bool IsValid(object value) { if (value == null) return true; if (value is string) { Regex regEx = new Regex(RegExString); return regEx.IsMatch(value.ToString()); } return false; } }
有幾點要注意的
- 必須將此類別宣告為 sealed class 。
- 需要繼承 ValidationAttribute 抽象類別
- 必須覆寫 IsValid 方法
接下來再將 RegisterModel 的 Email 屬性改一下
[Required] [DataType(DataType.EmailAddress)] [Display(Name = "Email")] [Email] public string Email { get; set; }
- 因為剛剛我們是建立在ValidateAttribute資料夾下因此預設的命名空間會變成 namespace MvcApplication1.ValidateAttribute 請記得 using
- 雖然類別名稱是 EmailAttribute 但因為是 Attribute 所以可以只輸入 Email
再次回到熟悉的註冊畫面吧,這時候請將該打的都打進去但 Email 欄位一樣打 123@123 按下「Register」
可以看到的確是有驗證,但僅有支援後端驗證,前端驗證是沒有的,原來自訂的那麼弱唷 當然不是,還記得 demo 一直提到的 IClientValidatable 介面嗎?因為剛剛建立 EmailAttribute 的時候我們沒有實作 IClientValidatable 介面因此前端驗證的部份是無效的,前端驗證可以加強使用者體驗,不加上怎麼可以,馬上回到 EmailAttribute 增加吧。
實作 IClientValidatable
public sealed class EmailAttribute : ValidationAttribute, IClientValidatable { public const string RegExString = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$"; public EmailAttribute() { } public override bool IsValid(object value) { if (value == null) return true; if (value is string) { Regex regEx = new Regex(RegExString); return regEx.IsMatch(value.ToString()); } return false; } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { ModelClientValidationRule rule = new ModelClientValidationRule { ValidationType = "email", ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()) }; yield return rule; }
IClientValidatable 只有一個要實作的方法GetClientValidationRules() 包含兩個參數
- ModelMetadata 是被驗證的 ModelMetadata (有點繞口)
- ControllerContext 是呼叫的 Controller資訊
再明確說明一下回傳的是什麼
ModelClientValidationRule rule = new ModelClientValidationRule { ValidationType = "email", ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()) }; yield return rule;
我們 New 了一個 ModelClientValidationRule 出來對於他的其中兩個屬性設定值
- ValidationType 顧名思義就是驗證型別,這是要給前端使用的請注意這一定要是小寫,不然會發生例外的
- ErrorMessage 是發生錯誤時的顯示訊息,目前利用了 FormatErrorMessage 方法來做一些額外的加工,詳細設定往後會提到,不在此次的討論範圍
ValidationType 一定要是小寫如果給了大寫就會引發錯誤
低調用戶端驗證規則中的驗證型別名稱必須只能包含小寫字母。
再來需要去增加 JS 中的相關驗證(你以為它能直接幫你產 JS 驗證邏輯嗎 開啟我們一直用來測試的註冊頁面 (/Account/Register)拉到最下面,讓我們動點手腳(註)
<script type="text/javascript"> $.validator.addMethod("email", function (value, element) { if (value == false) { return true; } this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value); }); $.validator.unobtrusive.adapters.addBool("email"); </script>
- 因為 EmailAttribute 的 ValidationType 是 email 因此上方都是增加 email
- 為了保障前後端的驗證是一樣的所以這裡使用的 Regex Pattern 一樣是從 jquery.validate.js 取得的
- 預設的情況下 ASP.NET MVC3 是會載入 unobtrusive 並開啟,如果您有調整過預設值就不在本文的討論範圍內
存檔後再回到註冊頁就可以看到前端驗證也正常執行了
最後補充幾點讀者可能會有疑惑的地方
前端的 Code 非常醜(其實是因為這個範例很醜)而且如果多頁需要還不是要複製貼上一堆頁面。
demo 的解決方法是建立一個專屬的 JS 檔來註冊與撰寫擴充驗證邏輯,這是 jQuery.Validate 原生就支援的功能,以往也都是這樣玩,如果你對於 JS 熟悉這種作法你應該很常用,而 JS 的相關處理技巧不在本系列討論範圍, demo 視情況再決定要不要再往後的文章中提出。
前後都有一段 Regex Pattern,很容易造成雙方不同步的情況
demo 的見解是前端驗證可以相對鬆散,後端驗證要相對嚴謹,所以即使往後要加強驗證邏輯只要和原本的不牴觸,你甚至可以只改後端,前端驗證僅有加強使用者體驗和避免不必要的資料送往後端的功能,千萬別相信前端擋住就可以擋的住什麼,前端驗證太容易破解了。
自訂擴充方法的還有幾篇的規劃,如果你對於這部份覺得有興趣也請不要錯過接下來的文章
都介紹完了以後讓我來拉杆一下
依據本範例所自訂的 Email 其實是不需要實作前端 JS 的部份就是有寫(註)的那一塊,因為 Email 的驗證預設就存在於 jQuery.vaildate 並且 jquery.validate.unobtrusive.js 早已註冊了 email 的驗證進去,但為求範例完整 demo 還是寫出了實作前端驗證的部份,並且提出相關要注意的事項,方便各位自訂其他驗證規則的時候使用,最後說明一下內建就可以用的驗證(您還是必須要補後端的部份)
- accept
- creditcard
- date
- digits
- number
- url
P.S. 這篇文章使用了五顆番茄
回應討論