閱讀531 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

沒有想到自己頭腦發熱寫了一個簡陋版本的所謂“驗證框架”能夠得到眾多網友的推薦。個人覺得這個驗證框架有兩個主要的特點是:;。《編程篇》中,我主要介紹了如何通過自定義特性的方式進行驗證規則的定義,在本篇中我主要來介紹該驗證框架的設計原理和實現。

一、核心三人組:Validator、ValidatorAttribute和ValidationError

應該說整個驗證框架的核心體係隻包含如下三中類型:Validator、ValidatorAttribute和ValidationError

image

  • 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之間的關係可以通過下麵的類圖表示。

image

五、最終的驗證如何進行?

到目前為止,構成驗證框架的所有核心的元素都已經介紹完成,現在我們來看看最終的驗證是如何進行的。在《編程篇》我們可以看到沒,我們最終是調用靜態外觀類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

  上一篇:go  采用一個自創的"驗證框架"實現對數據實體的驗證[編程篇]
  下一篇:go  采用一個自創的"驗證框架"實現對數據實體的驗證[改進篇]