閱讀281 返回首頁    go iPhone_iPad_Mac_apple


采用一個自創的"驗證框架"實現對數據實體的驗證[編程篇]

昨天晚上突發奇想,弄了一個簡易版的驗證框架,用於進行數據實體的驗證。目前僅僅實現基於屬性的聲明式的驗證,即通過自定義特性(Custom Attribute)的方式將相應的Validator應用到對應的屬性上,並設置相應的驗證規則。本篇文章分上下兩篇,上篇介紹如果來使用這個驗證框架,《下篇》介紹背後的設計原理和具體實現。

一、定義最簡單的驗證規則

我們先看看一個最簡單的驗證規則如何應用到對應的實體類型上。在這裏我們模擬一個有趣的場景:找對象,不論是找男朋友還是女朋友,還是不男不女的朋友,都具有一定的標準。在這裏我們把這些標準表示成“”。為了簡單,我們僅僅驗證對方的年齡是否符合我們的要求,為此我定義了如下一個簡單的Mate類型:

   1: public class Mate
   2: {
   3:     public int Age { get; set; }
   4:     public Mate(int age)
   5:     {
   6:         this.Age = age;
   7:     }
   8: }

現在我們對年齡設置一個最基本的要求:。為此我通過一個GreaterThanValidatorAttribute特性將一個應用到了Age屬性上,並指定了GreaterThanValidator的下限18,同時設置了驗證失敗後相應錯誤信息。值得一提的是:指定的驗證消息時一個消息模板,我們可以指定相應的站位符,比如{PropertyName}、{PropertyValue}、{Tag},它們分別表示對應屬性的屬性名、屬性值和自定義的Tag(在具體的ValidatorAttribute特性中設置,在本例中設置的Tag為“年齡”)。這些都是一般的、被所有Validator支持的站位符,具體的Validator也可以定義自己的站位符,比如{LowerBound}就是專屬於GreaterThanValidator特有。實際上還有其他一些站位符供你選擇。

   1: public class Mate
   2: {
   3:    private const string greaterThanMsg = "通過屬性{PropertyName}表示的{Tag}不能低於{LowerBound}周歲,當前{Tag}為{PropertyValue}周歲!";
   4:     
   5:     [GreaterThanValidator(greaterThanMsg, 18, Tag = "年齡")]   
   6:     public int Age { get; set; }
   7:  
   8:     public Mate(int age)
   9:     {
  10:         this.Age = age;
  11:     }
  12: }

然後我定義了如下一個靜態輔助方法Validate<T>進行對象的驗證,如果驗證成功,輸出“驗證成功”,否則輸出“驗證失敗”,並輸出格式化的出錯消息。

   1: static void Validate<T>(T objectToValidate)
   2: {
   3:     IEnumerable<ValidationError> validationErrors;
   4:     if (!Validation.Validate(objectToValidate, out validationErrors))
   5:     {
   6:         Console.WriteLine("驗證失敗!");
   7:         foreach (var error in validationErrors)
   8:         {
   9:             Console.WriteLine("\t"+ error.Message);
  10:         }
  11:     }
  12:     else
  13:     {
  14:         Console.WriteLine("驗證成功!");
  15:     }
  16: }

上述的Validate<T>主要是調用了我靜態類型Validation的Validate方法。該方法簽名如下:布爾類型的返回之表示是否驗證通過,輸出參數為一個對象集合,該對象表示具體出錯信息。

   1: public static class Validation
   2: {
   3:     public static bool Validate(object value, out IEnumerable<ValidationError> validationErrors)
   4: }

現在我們通過如下的代碼對實例化的Mate對象進行兩次驗證,第一次設置的年齡為20(符合要求),第二次為16(沒有成年,不符合要求)。輸出的結果表明驗證是按照我們期望的規則進行的。

   1: static void Main(string[] args)
   2: {
   3:     var mate = new Mate(20);
   4:     Validate<Mate>(mate);
   5:     mate.Age = 16;
   6:     Validate<Mate>(mate);
   7: }

輸出結果:

   1: 驗證成功!
   2: 驗證失敗!
   3:         通過屬性Age表示的年齡不能低於18周歲,當前年齡為16周歲!

二、多條件驗證規則

在大部分情況下,驗證規則不太可能通過單一的條件進行定義。就像你找女朋友的時候,不可能隻要是對方年滿18歲,你就照單全收一樣。如果你需要添加多個驗證條件,你隻需要。比如現在我們進一步修正我們的驗證規則:。那麼我們僅僅需要在Age屬性上添加一個額外的LessThanValidatorAttribute特性即可,該特性將應用到Age上麵。具體代碼如下所示,在LessThanValidatorAttribute設置了上限30,並製定了相應的驗證消息模板。

   1: public class Mate
   2: {
   3:     private const string greaterThanMsg = "通過屬性{PropertyName}表示的{Tag}不能低於{LowerBound}周歲,當前{Tag}為{PropertyValue}周歲!";
   4:     private const string lessThanMsg    = "通過屬性{PropertyName}表示的{Tag}不能高於{UpperBound}周歲,當前{Tag}為{PropertyValue}周歲!";
   5:     [GreaterThanValidator(greaterThanMsg, 18, Tag = "年齡")]
   6:     [LessThanValidator(lessThanMsg, 30, Tag = "年齡")]
   7:     public int Age { get; set; }
   8:  
   9:     public Mate(int age)
  10:     {
  11:         this.Age = age;
  12:     }
  13: }

現在我們稍微改動一下上麵的程序,將用於第二次驗證的Mate對象的Age屬性設置成。

   1: static void Main(string[] args)
   2: {
   3:     var mate = new Mate(20);
   4:     Validate<Mate>(mate);
   5:  
   6:     mate.Age = 50;
   7:     Validate<Mate>(mate);       
   8: }

執行程序,你將會得到如下的輸出:

   1: 驗證成功!
   2: 驗證失敗!
   3:         通過屬性Age表示的年齡不能高於30周歲,當前年齡為50周歲!

如果多個Validator應用到了同一個屬性,在執行驗證的時候都會被執行。。在此例中,返回的是LessThanValidator的ValidationError。但是有的時候我們希望獲取一個完整的錯誤信息,比如:“”。在這種情況下,你需要一個特殊的Validator:AndCompositeValidator。

三、試試AndCompositeValidator

AndCompositeValidator屬於一種特殊的Validator:,這中Validator本身不進行具體的驗證工作,而是。AndCompositeValidator對應的邏輯運算就是:,即所有ValidatorElement驗證通過才被認為是本Validator驗證通過。同樣對於上麵的驗證規則,我們就可以通過應用AndCompositeValidatorAttribute特性,指定一個“友好的”驗證消息。

   1: public class Mate
   2: {
   3:     private const string messageTemplate = "通過屬性{PropertyName}表示的{Tag}必須在18到30周歲之間,當前{Tag}為{PropertyValue}周歲!";
   4:     [AndCompositeValidator(messageTemplate,"greaterThan18,lessThan30",Tag="年齡")]
   5:     [GreaterThanValidatorElement("greaterThan18", 18)]
   6:     [LessThanValidatorElement("lessThan30", 30)]
   7:     public int Age { get; set; }
   8:  
   9:     public Mate(int age)
  10:     {
  11:         this.Age = age;
  12:     }
  13: }

雖然和上麵采用相同的驗證規則,但是這次我們采用的不是GreaterThanValidatorAttribute和LessThanValidatorAttribute,而是采用GreaterThanValidatorElementAttribute和LessThanValidatorElementAttribute。ValidatorElement服務於CompositeValidator,所有必須設置一個唯一的名稱,在這裏分別為:greaterThan18和lessThan30。

在AndCompositeValidatorAttribute中,(greaterThan18,lessThan30)。執行上麵的程序,這次的出現的驗證消息就是我們希望的了。

   1: 驗證成功!
   2: 驗證失敗!
   3:         通過屬性Age表示的年齡必須在18到30周歲之間,當前年齡為50周歲!

四、有AndCompositeValidator,自然就有OrCompositeValidator

AndCompositeValidator是基於“邏輯與”的CompositeValidator,自然就是基於“”的。OrCompositeValidator的用法和AndCompositeValidator完全一樣。如果我們將驗證規則改稱這樣:(這好像是“變態”的擇偶標準)。那麼我們的Mate類型就可以定義成如下的樣子。

   1: public class Mate
   2: {
   3:     private const string messageTemplate = "通過屬性{PropertyName}表示的{Tag}要麼大於30,要麼小於18,當前{Tag}為{PropertyValue}周歲!";
   4:     [OrCompositeValidator(messageTemplate,"greaterThan30,lessThan18",Tag="年齡")]
   5:     [GreaterThanValidatorElement("greaterThan30", 30)]
   6:     [LessThanValidatorElement("lessThan18", 18)]
   7:     public int Age { get; set; }
   8:  
   9:     public Mate(int age)
  10:     {
  11:         this.Age = age;
  12:     }
  13: }

現在我們需要對我們的驗證程序進行如下的修改。先將年齡設置成16(符合驗證規則),然後設置成20(不符合驗證規則)。執行程序,你也會得到期望的驗證失敗的錯誤消息。

   1: static void Main(string[] args)
   2: {
   3:     var mate = new Mate(16);
   4:     Validate<Mate>(mate);
   5:     mate.Age = 20;
   6:     Validate<Mate>(mate);
   7: }

輸出結果:

   1: 驗證成功!
   2: 驗證失敗!
   3:         通過屬性Age表示的年齡要麼大於30,要麼小於18,當前年齡為20周歲!

五、讓驗證規則來得更加複雜點吧

現在我們將驗證規則進一步升級:(現在很多女孩喜歡在成熟的老男人)。下麵的Mate類型的定義反映了這樣的驗證規則。

   1: public class Mate
   2: {
   3:     private const string messageTemplate = "通過屬性{PropertyName}表示的{Tag}或者在18到25周歲之間,或者大於40周歲,當前{Tag}為{PropertyValue}周歲!";
   4:  
   5:     [OrCompositeValidator(messageTemplate, "between18And25,greaterThan40",Tag="年齡")]
   6:     [AndCompositeValidatorElement("between18And25", "greaterThan18,lessThan25")]
   7:     [GreaterThanValidatorElement("greaterThan18", 18)]
   8:     [LessThanValidatorElement("lessThan25", 25)]
   9:     [GreaterThanValidatorElement("greaterThan40", 40)]
  10:     public int Age { get; set; }
  11:  
  12:     public Mate(int age)
  13:     {
  14:         this.Age = age;
  15:     }
  16: }

對於最新的驗證規則,最外層是“邏輯或”的關係,所以我們僅僅使用了一個唯一的OrOrCompositeValidator。組成該OrOrCompositeValidator的有兩個分支,分別通過兩個ValidatorElement表示。第一個ValidatorElement是一個AndCompositeValidatorElement(年齡在18到25周歲之間),後一個是GreaterThanValidatorElement(年齡高於40周歲)。而該AndCompositeValidatorElement又具有兩個分支,通過一個GreaterThanValidatorElement(年齡大於18周歲)和一個LessThanValidatorElement(年齡小於25周歲)表示。

現在我們修改驗證程序,分別將用於驗證的Mate對象的年齡設置成20(符合驗證規則)、50(符合驗證規則)、16(年紀太小,不符合驗證規則)和30(年紀既不年輕,也不夠成熟,不符合驗證規則),輸執行程序的輸出反映了最終得驗證結果。

   1: static void Main(string[] args)
   2: {
   3:     var mate = new Mate(20);
   4:     Validate<Mate>(mate);
   5:  
   6:     mate.Age = 50;
   7:     Validate<Mate>(mate);
   8:  
   9:     mate.Age = 16;
  10:     Validate<Mate>(mate);
  11:  
  12:     mate.Age = 30;
  13:     Validate<Mate>(mate);
  14: }

輸出結果:

   1: 驗證成功!
   2: 驗證成功!
   3: 驗證失敗!
   4:         通過屬性Age表示的年齡或者在18到25周歲之間,或者大於40周歲,當前年齡為16周歲!
   5: 驗證失敗!
   6:         通過屬性Age表示的年齡或者在18到25周歲之間,或者大於40周歲,當前年齡為30周歲!

六、對多驗證規則的支持

。對於“找對象”為例,不同的人具有不同的擇偶標準,同一個人在不同的年齡階段的擇偶標準也不可能相同。所以,一個好的驗證框架應該具有定義多中驗證規則的能力。

同樣以上麵的例子來說明,對於Mate類型,我希望為不同的人(比如張三和李四)定義不同的驗證規則。其中張三喜歡18歲到25周歲的年輕小MM,李四則鍾意35到40歲的中年婦女。那麼我們可以將兩種不同的驗證規則通過如下的代碼定義在Mate類型上。

   1: public class Mate
   2: {
   3:     private const string message4Zhangsan   = "通過屬性{PropertyName}表示的{Tag}必須在18到25周歲之間,當前{Tag}為{PropertyValue}周歲!";
   4:     private const string message4Lisi       = "通過屬性{PropertyName}表示的{Tag}必須在35到40周歲之間,當前{Tag}為{PropertyValue}周歲!";
   5:  
   6:     [AndCompositeValidator(message4Zhangsan, "greaterThan18,lessThan25", RuleName="Zhansan")]
   7:     [AndCompositeValidator(message4Lisi, "greaterThan35,lessThan40", RuleName = "Lisi")]
   8:     [GreaterThanValidatorElement("greaterThan18", 18)]
   9:     [GreaterThanValidatorElement("greaterThan35", 35)]
  10:     [LessThanValidatorElement("lessThan25", 25)]
  11:     [LessThanValidatorElement("lessThan40", 40)]
  12:     public int Age { get; set; }
  13:  
  14:     public Mate(int age)
  15:     {
  16:         this.Age = age;
  17:     }
  18: }

通過上麵的代碼我們可以看到:在Age屬性上應用了兩個AndCompositeValidator,它的屬性RuleName被設置成“zhansan”和“Lisi”,分別表示張三和李四的擇偶標準。對於這種設置了RuleName的情況,在進行驗證的時候需要具體的驗證個規則命名,表明當前驗證是基於那個具體的規則進行的。在靜態的外觀類Validation中,提供了另一個Validate方法重載,供你指定具體的驗證規則名稱。

   1: public static class Validation
   2: {    
   3:     public static bool Validate(object value, string ruleName, out IEnumerable<ValidationError> validationErrors)
   4:     {
   5:         //Others
   6:     }
   7: }

為了演示方便,我們改動一下之前定義的輔助方法Validate,為它添加一個額外的表示驗證規則名稱的參數ruleName,該方法最終會調用上麵的方法。

   1: static void Validate<T>(T objectToValidate, string ruleName)
   2: {
   3:     IEnumerable<ValidationError> validationErrors;
   4:     if (!Validation.Validate(objectToValidate,ruleName,out validationErrors))
   5:     {
   6:         Console.WriteLine("驗證失敗!");
   7:         foreach (var error in validationErrors)
   8:         {
   9:             Console.WriteLine("\t"+ error.Message);
  10:         }
  11:     }
  12:     else
  13:     {
  14:         Console.WriteLine("驗證成功!");
  15:     }
  16: }

那麼我們通過上麵的代碼驗證給定的Mate對象是否符合張三和李四的擇偶標準,被用於驗證的Mate對象的年齡分別為20和38周歲。從輸出結果來看,第一個(年齡為20)是張三喜歡的,但是曆史不喜歡;李四喜歡的是第二個(年齡為38),但是這個人不是張三中意的類型。

   1: static void Main(string[] args)
   2: {
   3:     var mate = new Mate(20);
   4:     Validate<Mate>(mate,"Zhansan");
   5:     Validate<Mate>(mate, "Lisi");
   6:  
   7:     mate.Age = 38;
   8:     Validate<Mate>(mate, "Zhansan");
   9:     Validate<Mate>(mate, "Lisi");           
  10: }

輸出結果:

   1: 驗證成功!
   2: 驗證失敗!
   3:         通過屬性Age表示的必須在35到40周歲之間,當前為20周歲!
   4: 驗證失敗!
   5:         通過屬性Age表示的必須在18到25周歲之間,當前為38周歲!
   6: 驗證成功!

如果對這個驗證框架的設計原理感興趣,敬請關注《下篇》。要先睹為快的朋友,可以從這裏下載源代碼。

 

采用一個自創的"驗證框架"實現對數據實體的驗證[編程篇]
采用一個自創的"驗證框架"實現對數據實體的驗證[設計篇]
采用一個自創的"驗證框架"實現對數據實體的驗證[改進篇]
采用一個自創的"驗證框架"實現對數據實體的驗證[擴展篇]


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-27 14:04:53

  上一篇:go  當InternalsVisibleToAttribute特性遭遇"強簽名"
  下一篇:go  采用一個自創的"驗證框架"實現對數據實體的驗證[設計篇]