采用一個自創的"驗證框架"實現對數據實體的驗證[編程篇]
昨天晚上突發奇想,弄了一個簡易版的驗證框架,用於進行數據實體的驗證。目前僅僅實現基於屬性的聲明式的驗證,即通過自定義特性(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