demoshop

demo, trying to be the best_

版本
3

在上一篇系列文中介紹了 ASP.NET MVC3 原生的驗證使用方法,是很好用但是稍嫌不足,如果今天我們要驗證欄位的值是否為 Email 的格式,不可能每次都組出 Regex 來驗證,這是會累死人的,而且也不能保證每次寫出來的 Regex 都一樣,所以自訂驗證規則就是很重要的了,在 ASP.NET MVC3 中可以輕鬆自在的實作 ValidationAttribute 和 IClientValidatable 來擴充需要的驗證邏輯,此篇文章就先來介紹最簡單的 是否驗證。

demo廢言是否為 電子郵件? 是否是身分證字號? 是否?是否?是否!? 網頁實作中經常性的需要驗證這種有固定格式的值,在上一篇的介紹中,您可能會利用 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 驗證


demo廢言看看上面的 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;
    }
}

有幾點要注意的

  1. 必須將此類別宣告為 sealed class 。
  2. 需要繼承 ValidationAttribute 抽象類別
  3. 必須覆寫 IsValid 方法

● 接下來再將 RegisterModel 的 Email 屬性改一下

[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email")]
[Email]
public string Email { get; set; }

注意事項

  1. 因為剛剛我們是建立在ValidateAttribute資料夾下因此預設的命名空間會變成 namespace MvcApplication1.ValidateAttribute 請記得 using
  2. 雖然類別名稱是 EmailAttribute 但因為是 Attribute 所以可以只輸入 Email

●再次回到熟悉的註冊畫面吧,這時候請將該打的都打進去但 Email 欄位一樣打 123@123 按下「Register」

demo廢言可以看到的確是有驗證,但僅有支援後端驗證,前端驗證是沒有的,原來自訂的那麼弱唷 當然不是,還記得 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() 包含兩個參數

  1. ModelMetadata 是被驗證的 ModelMetadata (有點繞口)
  2. 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>

注意事項

  1. 因為 EmailAttribute 的 ValidationType 是 email 因此上方都是增加 email
  2. 為了保障前後端的驗證是一樣的所以這裡使用的 Regex Pattern 一樣是從 jquery.validate.js 取得的
  3. 預設的情況下 ASP.NET MVC3 是會載入 unobtrusive 並開啟,如果您有調整過預設值就不在本文的討論範圍內

●存檔後再回到註冊頁就可以看到前端驗證也正常執行了


demo廢言最後補充幾點讀者可能會有疑惑的地方

前端的 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
  • email
  • number
  • url

 

P.S. 這篇文章使用了五顆番茄

回應討論