采用一個自創的"驗證框架"實現對數據實體的驗證[設計篇]
沒有想到自己頭腦發熱寫了一個簡陋版本的所謂“驗證框架”能夠得到眾多網友的推薦。個人覺得這個驗證框架有兩個主要的特點是:;。《編程篇》中,我主要介紹了如何通過自定義特性的方式進行驗證規則的定義,在本篇中我主要來介紹該驗證框架的設計原理和實現。
一、核心三人組:Validator、ValidatorAttribute和ValidationError
應該說整個驗證框架的核心體係隻包含如下三中類型:Validator、ValidatorAttribute和ValidationError
- Validator:所有的驗證邏輯均實現在相應的“驗證器”中,具體的驗證器均直接或者間接繼承自Validator這個抽象基類;
- ValidatorAttribute:上述的驗證器通過對應的自定義特性(Attribute)的方式應用到相應的數據實體類的屬性上,ValidatorAttribute是這些特性的基類;
- ValidationError:在Validator進行數據驗證的時候,如果數據實體對象順利通過驗證,則返回Null,否則驗證的錯誤信息封裝成一個ValidationError對象返回。
上麵的類圖反映了上述三個核心類型的屬性和操作,以及它們之間的關係。Validator通過Validate方法對傳入的數據實體進行驗證,驗證失敗的錯誤結果以ValidationError對象的形式返回;通過將相應的Validator應用到數據類型的目標屬性上的。
實際上,上述三個類型的定義都比較簡單,我們先來看看Validator這個抽象類的定義。如下麵的代碼所示,Validator具有一個MessageTemplate的屬性,表示,該模板具有一些預定義的占位符。這些占位符可以包括與的一般意義的對象,比如{PropertyName}、{PropertyValue}表示目標屬性名和屬性值,也包括一些的占位符,比如編程篇》提到的GreaterThanValidator的{LowerBound}和LessThanValidator的{UpperBound}。虛FormatMessage方法用於對MessageTemplate進行格式化,即通過相應的值來替換對應的占位符。在這裏將被驗證的值替換掉{PropertyValue}占位符。。
1: public abstract class Validator
2: {
3: public string MessageTemplate { get; protected set; }
4: public abstract ValidationError Validate(object objectToValidate);
5: public Validator(string messageTemplate)
6: {
7: this.MessageTemplate = messageTemplate ?? string.Empty;
8: }
9: public virtual void FormatMessage(object objectToValidate)
10: {
11: this.MessageTemplate = this.MessageTemplate.Replace("{PropertyValue}", objectToValidate.ToString());
12: }
13: protected ValidationError CreateValidationError(object objectToValidate)
14: {
15: Guard.ArgumentNotNull(objectToValidate, "objectToValidate");
16: return new ValidationError(this.MessageTemplate, objectToValidate, this);
17: }
18: }
我們接著分析ValidatorAttribute的定義。如下麵提供的代碼片斷所示,這是一個繼承自Attribute的抽象類。MessageTemplate屬性無需多說,RuleName屬性表示驗證規則的名稱。而是為了靈活實現對消息模板格式化的需要,你可以在MessageTemplate中定義{Tag}占位符,然後通過該屬性指定替換它的值。ValidatorAttribute同樣定義需方法FormatMessage,在這裏我們用屬性名稱替換{PropertyName}占位符。我們的框架最終需要通過ValidatorAttribute創建相應的Validator,這個操作以抽象方法方法提供。
1: [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
2: public abstract class ValidatorAttribute: Attribute
3: {
4: public string RuleName { get; set; }
5: public string Tag { get; set; }
6: public string MessageTemplate { get; private set; }
7: public abstract Validator CreateValidator();
8:
9: public ValidatorAttribute(string messageTemplate)
10: {
11: Guard.ArgumentNotNullOrEmpty(messageTemplate, "messageTemplate");
12: this.MessageTemplate = messageTemplate;
13: this.RuleName = string.Empty;
14: }
15: public virtual void FormatMessage(PropertyInfo property)
16: {
17: this.MessageTemplate = this.MessageTemplate.Replace("{PropertyName}", property.Name)
18: .Replace("{Tag}", this.Tag);
19: }
20: }
表示驗證失敗信息的ValidationError,我盡量將其定義的簡單一點。它包含Message、Target和Validtor三個屬性,分別表示錯誤消息、驗證的目標對象和采用的Validator。
1: public class ValidationError
2: {
3: public string Message { get; internal set; }
4: public object Target { get; internal set; }
5: public Validator Validator { get; internal set; }
6:
7: public ValidationError(string message, object target, Validator validator)
8: {
9: Guard.ArgumentNotNull(message, "message");
10: Guard.ArgumentNotNull(target, "target");
11: Guard.ArgumentNotNull(validator, "validator");
12:
13: this.Message = message;
14: this.Target = target;
15: this.Validator = validator;
16: }
17: }
二、一個特殊但卻意義重大的Validator:CompositeValidator
正如開篇所說,這個框架一個重要的可取之處在於能夠提供的支持,而這是通過這種特殊的Validator來實現的。我們提要提供兩種具體的CompositeValidator:和,分別用於進行“”和“”的邏輯判斷。它們具有一個相同的抽象基類——CompositeValidator。下麵提供的代碼片斷表明,。我將Validators集合中的每一個Validator成為構成CompositeValidator的。
1: public abstract class CompositeValidator:Validator
2: {
3: public CompositeValidator(string messageTemplate, IEnumerable<Validator> validators):base(messageTemplate)
4: {
5: Guard.ArgumentNotNull(validators, "validators");
6: this.Validators = validators;
7: }
8: public IEnumerable<Validator> Validators { get; private set; }
9: }
而AndCompositeValidator和OrCompositeValidator定義也很簡單,不用多說你也能夠看明白具體采用的驗證邏輯。
1: public class AndCompositeValidator: CompositeValidator
2: {
3: public AndCompositeValidator(string messageTemplate,IEnumerable<Validator> validators)
4: : base(messageTemplate,validators)
5: { }
6:
7: public override ValidationError Validate(object objectToValidate)
8: {
9: foreach (var validator in this.Validators)
10: {
11: if (null != validator.Validate(objectToValidate))
12: {
13: return this.CreateValidationError(objectToValidate);
14: }
15: }
16: return null;
17: }
18: }
1: public class OrCompositeValidator : CompositeValidator
2: {
3: public OrCompositeValidator(string messageTemplate, IEnumerable<Validator> validators)
4: : base(messageTemplate,validators){ }
5:
6: public override ValidationError Validate(object objectToValidate)
7: {
8: foreach (var validator in this.Validators)
9: {
10: var validationError = validator.Validate(objectToValidate);
11: if (null == validationError)
12: {
13: return null;
14: }
15: }
16: return this.CreateValidationError(objectToValidate);
17: }
18: }
基於重用的目的,我們會CompositeValidator定義了ValidatorAttribute基類:CompositeValidatorAttribute。我們。
1: public abstract class CompositeValidatorAttribute : ValidatorAttribute
2: {
3: public IEnumerable<string> ValidatorElements { get; private set; }
4: public CompositeValidatorAttribute(string messageTemplate, string validatorElements):base(messageTemplate)
5: {
6: Guard.ArgumentNotNullOrEmpty(validatorElements, "validatorElements");
7: this.ValidatorElements = validatorElements.Split(',');
8: }
9: public abstract CompositeValidator CreateCompositeValidator(IEnumerable<Validator> validator);
10: public override Validator CreateValidator()
11: {
12: throw new NotImplementedException();
13: }
14: }
三、驗證器元素(ValidatorElement)如何定義?
我們所有的驗證規則均通過自定義特性(Attribute)的方式進行定義,說白了就是通過特性的方式將相應的Validator應用到數據類型的目標屬性中去。Validator通過ValidatorAttribute可以方便地進行應用,但是構成上述CompositeValidator的驗證器元素有如何應用呢?在這裏我創建了另一種類型的特性——。和ValidatorAttribute不同,ValidatorElementAttribute沒有定義MessageTemplate屬性,應為最終的驗證消息通過CompositeValidator來提供。ValidatorElementAttribute提供了一個Name屬性,表示該驗證器元素的唯一標識。ValidatorElementAttribute和ValidatorAttribute一樣,最終。
1: [AttributeUsage(AttributeTargets.Property, AllowMultiple =true)]
2: public abstract class ValidatorElementAttribute:Attribute
3: {
4: public string Name { get; private set; }
5: public ValidatorElementAttribute(string name )
6: {
7: Guard.ArgumentNotNullOrEmpty(name, "name");
8: this.Name = name;
9: }
10: public abstract Validator CreateValidator();
11: }
四、CompositeValidator需要有特殊的ValidatorElementAttribute
對於任何一個具體的Validator,由於它既可以作為獨立的驗證器進行數據驗證工作,也可以作為CompositeValidator的驗證器元素協同其他的Validator一起完成複雜邏輯判斷。所以。原因很簡單,。
但是基於CompositeValidator的ValidatorElementAttribute有點特別,以至於我們不得不為之定義一個單獨的基類:CompositeValidatorElementAttribute。由於。
1: public abstract class CompositeValidatorElementAttribute : ValidatorElementAttribute
2: {
3: public IEnumerable<string> ValidatorElements { get; private set; }
4: public CompositeValidatorElementAttribute(string name, string validatorElements)
5: : base(name)
6: {
7: Guard.ArgumentNotNullOrEmpty(validatorElements, "validatorElements");
8: this.ValidatorElements = validatorElements.Split(',');
9: }
10: public abstract CompositeValidator CreateCompositeValidator(IEnumerable<Validator> validators);
11: public override Validator CreateValidator()
12: {
13: throw new NotImplementedException();
14: }
15: }
以上我們著重在介紹CompositeValidator的設計,CompositeValidator以及相關的ValidatorElementAttribute和CompositeValidatorElementAttribute之間的關係可以通過下麵的類圖表示。
五、最終的驗證如何進行?
到目前為止,構成驗證框架的所有核心的元素都已經介紹完成,現在我們來看看最終的驗證是如何進行的。在《編程篇》我們可以看到沒,我們最終是調用靜態外觀類Validation的Validate方法對數據實體對象進行驗證的。具體來說我們定義了如下兩個Validate重載,其正一個可以指定驗證規則名稱。
1: public static class Validation
2: {
3: public static bool Validate(object value, out IEnumerable<ValidationError> validationErrors)
4: {
5: return Validate(value,string.Empty, out validationErrors);
6: }
7:
8: public static bool Validate(object value, string ruleName, out IEnumerable<ValidationError> validationErrors)
9: {
10: //省略
11: }
12: }
最終的驗證邏輯都實現在帶ruleName參數的Validate方法中,下麵是該方法的定義。隻要的邏輯就是:通過反射獲取驗證對象類型的共有PropertyInfo,並通過它和驗證規則名稱得到匹配的Validator的列表,然後用它們對屬性的值進行驗證。
1: public static bool Validate(object value, string ruleName, out IEnumerable<ValidationError> validationErrors)
2: {
3: validationErrors = new List<ValidationError>();
4: Guard.ArgumentNotNull(value, "value");
5: foreach (var property in value.GetType().GetProperties())
6: {
7: var validators = GetValidators(ruleName, property);
8: if (validators.Count() > 0)
9: {
10: var propertyValue = property.GetValue(value, null);
11: foreach (var validator in validators)
12: {
13: validator.FormatMessage(propertyValue);
14: var error = validator.Validate(propertyValue);
15: if (null != error)
16: {
17: ((List<ValidationError>)validationErrors).Add(error);
18: }
19: }
20: }
21: }
22: return validationErrors.Count() == 0;
23: }
實際上Validate方法中最複雜的邏輯在於如何通過PropertyInfo和驗證規則的名稱獲取匹配的Validator的列表。主要的思路還是通過PropertyInfo進行反射獲取應用在上麵的ValidatorAttribute,並通過得到ValidatorAttribute創建相應的Validator。不過這其中涉及到,以及的,代碼相對較多,在這裏不作具體介紹了。有興趣的朋友可以從這裏下載源代碼進行分析。
六、更多的考慮
和我很多文章一樣,這篇文章僅僅是為某個應用場景提供一種大體上的思路,或者提供一種最為簡單的解決方案。如果你需要將這些半理論的東西用於實踐,你應該根據具體的需求做更多的考慮。本文提供的所謂的“驗證框架”我之所以打上引號,是因為其粗陋不堪,實際上就是我花了不到3個小時寫成的一個小程序。不過,對於這個小程序背後的解決問題的思路,我覺得還是可取的。有心的朋友不妨從下麵的方麵對這個“框架”進行擴充:
- 通過配置+特性的方式是驗證規則的變得更加靈活;
- Validator不僅僅能夠應用於屬性,可以考慮字段;
- Validator不僅僅能夠應用公有成員,或許可以考慮非公有成員;
- Validator不僅僅可以應用於屬性、字段,也可以應用於整個類型,這就可以對整個對象級別定義驗證規則,比如驗證StartDate〈EndDate(StartDate和EndDate對應兩個屬性);
- Validator還可以應用於方法的參數;
- 考慮和相應AOP框架集成,讓驗證(主要是參數驗證)自動完成;
- 如果你希望將Validator應用於WCF服務或者契約方法的參數,可以考慮通過WCF擴展讓驗證工作自動執行;
- 通過Resource的方式定義驗證消息模板,可以獲得多語言文化的支持
- 其他
采用一個自創的"驗證框架"實現對數據實體的驗證[編程篇]
采用一個自創的"驗證框架"實現對數據實體的驗證[設計篇]
采用一個自創的"驗證框架"實現對數據實體的驗證[改進篇]
采用一個自創的"驗證框架"實現對數據實體的驗證[擴展篇]
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 14:04:51