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


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

自《編程篇》和《設計篇》發布以來,收到了一些反饋。尤其是園友雙魚座提到.NET 3.5下的System.ComponentModel.DataAnnotations命名空間具有相似的實現,並且通過“表達式”的方式實現了CompositeValidator的服務邏輯判斷的功能。為此,我對這個“驗證框架”進行了相應的改進,。為了讓大家對此改進又一個深刻的認識,我們來對比之下對於同一個驗證規則,改進前後有何不同。[源代碼從這裏下載]

一、改進前如何使用CompositeValidator?

改進前的CompositeValidator僅僅提供對兩種基本邏輯運算(“析取”和“或取”)的支持,他們分別通過兩個具體的CompositeValidator實現,即AndCompositeValidator和OrCompositeValidator。那麼對於一些相對複雜的驗證規則,。通過采用《編程篇》中的“找對象”的例子,如果對年齡具有這樣的要求:,對於這段不算太複雜的驗證規則,我們需要在Age屬性上添加如下7個特性。

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

采用《編程篇》中定義的輔助方法Validate,我們通過如下的代碼對具有不同Age屬性值的Mate對象實施驗證。對於不同的年齡——16(不符合要求)、20(符合要求)、28(不符合要求)、45(符合要求)和60(不符合要求),分別具有不同的驗證結果。

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

輸出結果:

   1: 驗證失敗!
   2:         通過屬性Age表示的年齡要麼在18到25周歲之間,要麼在40到50周歲之間。 當前年齡為16周歲!
   3: 驗證成功!
   4: 驗證失敗!
   5:         通過屬性Age表示的年齡要麼在18到25周歲之間,要麼在40到50周歲之間。 當前年齡為28周歲!
   6: 驗證成功!
   7: 驗證失敗!
   8:         通過屬性Age表示的年齡要麼在18到25周歲之間,要麼在40到50周歲之間。 當前年齡為60周歲!

二、在新的CompositeValidator中使用表達式來定義驗證規則

如果你采用改進後的驗證框架,上麵的驗證規則可以通過表達式的形式直接寫在CompositeValidatorAttribute特性中,下麵的代碼為你展示了相應的采用改進後的CompositeValidator的Mate類型該如果定義。從中我們可以看出,上述的驗證規則對應的表達式為:,不但直觀而且簡潔。在這段表達式中通過“V:{ValidatorElementName}” 表示相應ValidatorElement最終的驗證結果(True或者False)。“V: greaterThan18 ”表示應用在Age屬性上名稱為greaterThan18的GreaterThanValidatorElementAttribute對應的GreaterThanValidator對Age屬性值最終的驗證結果。目前為止,驗證規則對應的表達式支持三種基本的邏輯操作:AND、OR和NOT,這三個操作符結合括號將會使你的驗證表達式具有很強的表達能力。如果還不夠,你甚至可以定義更多的邏輯操作符。

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

三、最新的CompositeValidator作了哪些改變?

在之前的版本中,CompositeValidator是一個抽象類,我們需要定義繼承自該類的具體的CompositeValidator來完成相應的邏輯運算,比如AndCompositeValidator和OrCompositeValidator。在新的版本中,CompositeValidator本身就是一個可以用於驗證的Validator。我們為它指定一個驗證表達式,它自己可以對表達式進行解析,並調用相應的ValidatorElement實施單一驗證。最終將這些單一驗證結果按照表達式定義的邏輯關係,得到一個最終的結果。最新的CompositeValidator定義如下:

   1: public  class CompositeValidator:Validator
   2: {
   3:     public string ValidationExpression { get; private set; }
   4:     public ExpressionParser ExpressionParser { get; private set; }
   5:     public IEnumerable<Validator> Validators { get; private set; }
   6:  
   7:     public CompositeValidator(string messageTemplate, string validationExpression,IEnumerable<Validator> validators):base(messageTemplate)
   8:     {
   9:         Guard.ArgumentNotNullOrEmpty(validationExpression, "validationExpression");
  10:         Guard.ArgumentNotNull(validators, "validators");
  11:         this.ValidationExpression = validationExpression;
  12:         this.Validators = validators;
  13:         this.ExpressionParser = new ExpressionParser(validators);
  14:     }
  15:     public override ValidationError Validate(object objectToValidate)
  16:     {
  17:         if (this.ExpressionParser.Parse(this.ValidationExpression).Evaluate(objectToValidate))
  18:         {
  19:             return null;
  20:         }
  21:         else
  22:         {
  23:             return this.CreateValidationError(objectToValidate);
  24:         }
  25:     }
  26: }

通過可以看到,CompositeValidator多了兩個隻讀屬性:ValidationExpression和ExpressionParser,分別表示驗證表達式和表達式解析器(Parser)。那麼在Validate方法中,直接調用ExpressionParser的Parse方法會得到一個自定義的BooleanExpression對象,直接調用該對象的Evaluate方法並傳入驗證對象作為參數,就可以得到最終的驗證結果。

CompositeValidatorAttribute隨著CompositeValidator的改進也作了相應的變化。隻要是提供了一個表示驗證表達式的ValidationExpression的屬性,該屬性在構造函數中指定。和之前的版本一樣,CompositeValidatorAttribute不得不采用顯式指定ValidatorElment列表的方式(調用CreateCompositeValidator方法來創建CompositeValidator對象)。

   1: public class CompositeValidatorAttribute : ValidatorAttribute
   2: {
   3:     public string ValidationExpression { get; private set; }
   4:     public CompositeValidatorAttribute(string messageTemplate, string validationExpression)
   5:         : base(messageTemplate)
   6:     {
   7:         Guard.ArgumentNotNullOrEmpty(validationExpression, "validationExpression");
   8:         this.ValidationExpression = validationExpression;
   9:         
  10:     }
  11:     public CompositeValidator CreateCompositeValidator(IEnumerable<Validator> validators)
  12:     {
  13:         return new CompositeValidator(this.MessageTemplate, this.ValidationExpression, validators);
  14:     }
  15:     public override Validator CreateValidator()
  16:     {
  17:         throw new NotImplementedException();
  18:     }
  19: }

對於ExpressionParser得實現,我是借鑒了。由於邏輯稍微有點複雜,有興趣的朋友可以分析一下EnterLib的源碼,也可以直接下載本驗證框架的源代碼分析表達式解析的邏輯。

四、最終的驗證邏輯變得簡單

表達式的引入使CompositeValidator和它們ValidatorElement變成是(原來還有一個CompositeValidatorElement的概念),。我將所有的代碼寫在下麵,裏麵主要涉及到的邏輯就是如果得到某個屬性相匹配的Validator。為了避免頻繁的反射和對Validator的創建,我參數了緩存機製。不過代碼行數還是不少,如果對此真的感興趣的話還是下載源代碼吧。

   1: public static class Validation
   2: {
   3:     private static Dictionary<NamedProperty, IEnumerable<Validator>> validators = new Dictionary<NamedProperty, IEnumerable<Validator>>();
   4:     private static Dictionary<NamedProperty, IEnumerable<Validator>> validatorElements = new Dictionary<NamedProperty, IEnumerable<Validator>>();
   5:  
   6:     private static IEnumerable<Validator> GetValidators(string ruleName, PropertyInfo property)
   7:     {
   8:         var key = new NamedProperty(ruleName, property);
   9:         if (validators.ContainsKey(key))
  10:         {
  11:             return validators[key];
  12:         }
  13:         lock (typeof(Validation))
  14:         {
  15:             if (validators.ContainsKey(key))
  16:             {
  17:                 return validators[key];
  18:             }
  19:             validators[key] = DoGetValidators(ruleName,property);
  20:             return validators[key];
  21:         }
  22:     }
  23:  
  24:     private static IEnumerable<Validator>  GetValidatorElements(string ruleName, PropertyInfo property)
  25:     {
  26:          var key = new NamedProperty(ruleName, property);
  27:          if (validatorElements.ContainsKey(key))
  28:         {
  29:             return validatorElements[key];
  30:         }
  31:         lock (typeof(Validation))
  32:         {
  33:             if (validatorElements.ContainsKey(key))
  34:             {
  35:                 return validatorElements[key];
  36:             }
  37:             validatorElements[key] = DoGetValidatorElements(ruleName, property);
  38:             return validatorElements[key];
  39:         }
  40:     }
  41:  
  42:     private static IEnumerable<Validator> DoGetValidatorElements(string ruleName, PropertyInfo property)
  43:     {
  44:         foreach (ValidatorElementAttribute attribute in property.GetCustomAttributes(typeof(ValidatorElementAttribute), true))
  45:         {
  46:             yield return attribute.CreateValidator();
  47:         }
  48:     }
  49:  
  50:     private static IEnumerable<Validator> DoGetValidators(string ruleName, PropertyInfo property)
  51:     {
  52:         var validatorAttributes = new List<ValidatorAttribute>();
  53:  
  54:         foreach (var attribute in property.GetCustomAttributes(typeof(ValidatorAttribute), true))
  55:         {
  56:             ValidatorAttribute validatorAttribute = (ValidatorAttribute)attribute;
  57:             if (validatorAttribute.RuleName == ruleName)
  58:             {
  59:                 validatorAttribute.FormatMessage(property);
  60:                 validatorAttributes.Add(validatorAttribute);
  61:             }
  62:         }
  63:  
  64:         foreach (var attribute in validatorAttributes)
  65:         {
  66:             if (attribute is CompositeValidatorAttribute)
  67:             {
  68:                 yield return ((CompositeValidatorAttribute)attribute).CreateCompositeValidator(GetValidatorElements(ruleName, property));
  69:             }
  70:             else
  71:             {
  72:                 yield return attribute.CreateValidator();
  73:             }
  74:         }
  75:     }
  76:  
  77:     public static bool Validate(object value, out IEnumerable<ValidationError> validationErrors)
  78:     {
  79:         return Validate(value,string.Empty, out validationErrors);
  80:     }
  81:  
  82:     public static bool Validate(object value, string ruleName, out IEnumerable<ValidationError> validationErrors)
  83:     {
  84:         validationErrors = new List<ValidationError>();
  85:         Guard.ArgumentNotNull(value, "value");
  86:         foreach (var property in value.GetType().GetProperties())
  87:         {
  88:             var validators = GetValidators(ruleName, property);
  89:             if (validators.Count() > 0)
  90:             {
  91:                 var propertyValue = property.GetValue(value, null);
  92:                 foreach (var validator in validators)
  93:                 {
  94:                     validator.FormatMessage(propertyValue);
  95:                     var error = validator.Validate(propertyValue);
  96:                     if (null != error)
  97:                     {
  98:                         ((List<ValidationError>)validationErrors).Add(error);
  99:                     }
 100:                 }
 101:             }
 102:         }
 103:         return validationErrors.Count() == 0;
 104:     }
 105: }
 106:  
 107: internal class NamedProperty
 108: {
 109:     public string RuleName { get; set; }
 110:     public PropertyInfo Property { get; set; }
 111:  
 112:     public NamedProperty(string name, PropertyInfo property)
 113:     {
 114:         this.RuleName = name ?? string.Empty;
 115:         Guard.ArgumentNotNull(property, "property");
 116:         this.Property = property;
 117:     }
 118:  
 119:     public override int GetHashCode()
 120:     {
 121:         return this.RuleName.GetHashCode() ^ this.Property.GetHashCode();
 122:     }
 123: }

 

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


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

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

  上一篇:go  采用一個自創的"驗證框架"實現對數據實體的驗證[設計篇]
  下一篇:go  采用一個自創的"驗證框架"實現對數據實體的驗證[擴展篇]