閱讀843 返回首頁    go iPhone_iPad_Mac_apple


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

關於“驗證框架”,先後推出了《編程篇》、《設計篇》和《改進篇》,本不打算再寫《XXX篇》的。但是今天收到兩個園友的短消息,想了解一下如何定義自己的驗證規則。這實際上涉及到對該“驗證框架”的擴展,即如何自定義Validator和對應的ValidatorAttribute與ValidatorElementAttribute。為了讓本係列看起來完整,通過《擴展篇》進行收尾。本篇我們寫一個簡單的Validator,用於

一、創建一個自定義Validator:StringLengthValidator

StringLengthValidator數據實體類型的字符串屬性進行校驗,確保它的長度符合要求(比如小於或者等於數據庫中該列的最大長度)。這是一個非常簡單的驗證邏輯,隻需驗證大於(或者大於等於)執行的長度下限,小於(或者小於等於)指定的長度上限就可以了。由於有時候,有時候被驗證的字符可以,有時則。為了代表這樣的比較方式,我定義如下RangeBoundaryType枚舉。Ignore、Iclusive和Exclusive分別表示。

   1: public enum RangeBoundaryType
   2: {
   3:     Ignore,
   4:     Inclusive,
   5:     Exclusive
   6: } 

StringLengthValidator的整個定義如下所示,定義在Validate方法中的驗證邏輯簡單得令人發指,應該無需多做介紹吧。唯一值得一提的是,基於StringLengthValidator的驗證消息模板。如果你願意,還可以將{LowerBoundType}和{UpperBoundType}作為占位符。

   1: public class StringLengthValidator: Validator
   2: {
   3:     public int LowerBound { get;  private set; }
   4:     public int UpperBound { get; private set; }
   5:     public RangeBoundaryType LowerBoundType { get; private set; }
   6:     public RangeBoundaryType UpperBoundType { get; private set; }
   7:  
   8:     public StringLengthValidator(string messageTemplate, int lowerBound, RangeBoundaryType lowerBoundType, int upperBound, RangeBoundaryType upperBoundType)
   9:         : base(messageTemplate)
  10:     {
  11:         this.LowerBound = lowerBound;
  12:         this.UpperBound = upperBound;
  13:         this.LowerBoundType = lowerBoundType;
  14:         this.UpperBoundType = upperBoundType;
  15:     }
  16:  
  17:     public override ValidationError Validate(object objectToValidate)
  18:     {
  19:         Guard.ArgumentNotNull(objectToValidate, "objectToValidate");
  20:         if (this.LowerBound > this.UpperBound)
  21:         {
  22:             throw new ArgumentException("UpperBound must not be less than LowerBound!");
  23:         }
  24:         string strValue = objectToValidate as string;
  25:         if (null == strValue)
  26:         {
  27:             throw new ArgumentException("The object to validate must be string!", "objectToValidate");
  28:         }
  29:  
  30:         bool greaterThanLowBound = (this.LowerBoundType == RangeBoundaryType.Ignore) ||
  31:             (strValue.Length > this.LowerBound && this.LowerBoundType == RangeBoundaryType.Exclusive) ||
  32:             (strValue.Length >= this.LowerBound && this.LowerBoundType == RangeBoundaryType.Inclusive);
  33:         bool lessThanUpperBound = (this.UpperBoundType == RangeBoundaryType.Ignore) ||
  34:            (strValue.Length < this.UpperBound && this.UpperBoundType == RangeBoundaryType.Exclusive) ||
  35:            (strValue.Length <= this.UpperBound && this.UpperBoundType == RangeBoundaryType.Inclusive);
  36:  
  37:         if (greaterThanLowBound && lessThanUpperBound)
  38:         {
  39:             return null;
  40:         }
  41:         else
  42:         {
  43:             return this.CreateValidationError(objectToValidate);
  44:         }
  45:     }
  46:  
  47:     public override void FormatMessage(object objectToValidate)
  48:     {
  49:         base.FormatMessage(objectToValidate);
  50:         this.MessageTemplate = this.MessageTemplate.Replace("{LowerBound}", this.LowerBound.ToString())
  51:             .Replace("{UpperBound}", this.UpperBound.ToString());
  52:     }
  53: }

二、為StringLengthValidator創建ValidatorAttribute

自定義的Validator最終通過特性的方式應用到數據實體類型的目標屬性上實施驗證,所以我們需要為StringLengthValidator定義相應的特性:。StringLengthValidatorAttribute定義如下,簡單起見,我沒有在構造函數中指定StringLengthValidator的四個屬性,而是讓開發者通過屬性名稱顯式地設定。LowerBound、UpperBound、LowerBoundType和UpperBoundType的默認值為Int32.MinValue、Int32.MaxValue、Ignore和Ingore。

   1: public class StringLengthValidatorAttribute : ValidatorAttribute
   2: {
   3:     public int LowerBound { get;  set; }
   4:     public int UpperBound { get;  set; }
   5:     public RangeBoundaryType LowerBoundType { get;  set; }
   6:     public RangeBoundaryType UpperBoundType { get;  set; }
   7:  
   8:     public StringLengthValidatorAttribute(string messageTemplate)
   9:         : base(messageTemplate)
  10:     {
  11:         this.LowerBound = int.MinValue;
  12:         this.UpperBound = int.MaxValue;
  13:         this.LowerBoundType = RangeBoundaryType.Ignore;
  14:         this.UpperBoundType = RangeBoundaryType.Ignore;
  15:     }
  16:  
  17:     public override Validator CreateValidator()
  18:     {
  19:         return new StringLengthValidator(this.MessageTemplate, this.LowerBound, this.LowerBoundType, this.UpperBound, this.UpperBoundType);
  20:     }
  21: }

現在我們將StringLengthValidatorAttribute用於自定義的Foo類型的Bar屬性上,定義了4個驗證規則要求該屬性表示的字符長度:;;;。

   1: public class Foo
   2: {
   3:     private const string message4Rule1 = "屬性{PropertyName}的長度必須在{LowerBound}(含{LowerBound})與{UpperBound}(含{UpperBound})之間。";
   4:     private const string message4Rule2 = "屬性{PropertyName}的長度必須在{LowerBound}(不含{LowerBound})與{UpperBound}(不含{UpperBound})之間。";
   5:     private const string message4Rule3 = "屬性{PropertyName}的長度必須大於{LowerBound}。";
   6:     private const string message4Rule4 = "屬性{PropertyName}的長度必須小於{UpperBound}。";
   7:  
   8:     [StringLengthValidator(message4Rule1,LowerBound  = 6, LowerBoundType = RangeBoundaryType.Inclusive, UpperBound = 10,UpperBoundType = RangeBoundaryType.Inclusive, RuleName= "rule1")]
   9:     [StringLengthValidator(message4Rule2, LowerBound = 6, LowerBoundType = RangeBoundaryType.Exclusive, UpperBound = 10, UpperBoundType = RangeBoundaryType.Exclusive, RuleName = "rule2")]
  10:     [StringLengthValidator(message4Rule3, LowerBound = 6, LowerBoundType = RangeBoundaryType.Exclusive, RuleName = "rule3")]
  11:     [StringLengthValidator(message4Rule4, LowerBound = 6, UpperBound = 10, UpperBoundType = RangeBoundaryType.Exclusive, RuleName = "rule4")]
  12:     public string Bar { get; set; }
  13: }

現在我們通過如下的靜態輔助方法Validate基於指定的驗證規則實施驗證:

   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("\t驗證失敗:");
   7:         foreach (var error in validationErrors)
   8:         {
   9:             Console.WriteLine("\t\t"+ error.Message);
  10:         }
  11:     }
  12:     else
  13:     {
  14:         Console.WriteLine("\t驗證成功!");
  15:     }
  16: }

具體的驗證代碼如下。根據指定的字符長度上下限(6和10),我們分別將Bar屬性的字符長度先後設置成。從執行程序得到的輸出可以看出我們的代碼執行的驗證工作是正確的。

   1: static void Main(string[] args)
   2: {
   3:     var foo = new Foo();
   4:     Console.WriteLine("當前字符長度:{0}", 4);
   5:     foo.Bar = "1234";
   6:     Validate<Foo>(foo, "rule1");
   7:     Validate<Foo>(foo, "rule2");
   8:     Validate<Foo>(foo, "rule3");
   9:     Validate<Foo>(foo, "rule4");
  10:  
  11:     Console.WriteLine("當前字符長度:{0}", 6);
  12:     foo.Bar = "123456";
  13:     Validate<Foo>(foo, "rule1");
  14:     Validate<Foo>(foo, "rule2");
  15:     Validate<Foo>(foo, "rule3");
  16:     Validate<Foo>(foo, "rule4");
  17:  
  18:     Console.WriteLine("當前字符長度:{0}", 8);
  19:     foo.Bar = "12345678";
  20:     Validate<Foo>(foo, "rule1");
  21:     Validate<Foo>(foo, "rule2");
  22:     Validate<Foo>(foo, "rule3");
  23:     Validate<Foo>(foo, "rule4");
  24:  
  25:     Console.WriteLine("當前字符長度:{0}", 10);
  26:     foo.Bar = "1234567890";
  27:     Validate<Foo>(foo, "rule1");
  28:     Validate<Foo>(foo, "rule2");
  29:     Validate<Foo>(foo, "rule3");
  30:     Validate<Foo>(foo, "rule4");
  31:  
  32:     Console.WriteLine("當前字符長度:{0}", 12);
  33:     foo.Bar = "123456789012";
  34:     Validate<Foo>(foo, "rule1");
  35:     Validate<Foo>(foo, "rule2");
  36:     Validate<Foo>(foo, "rule3");
  37:     Validate<Foo>(foo, "rule4");
  38:  
  39: }

 

輸出結果:

   1: 當前字符長度:4
   2:         驗證失敗:
   3:                 屬性Bar的長度必須在6(含6)與10(含10)之間。
   4:         驗證失敗:
   5:                 屬性Bar的長度必須在6(不含6)與10(不含10)之間。
   6:         驗證失敗:
   7:                 屬性Bar的長度必須大於6。
   8:         驗證成功!
   9: 當前字符長度:6
  10:         驗證成功!
  11:         驗證失敗:
  12:                 屬性Bar的長度必須在6(不含6)與10(不含10)之間。
  13:         驗證失敗:
  14:                 屬性Bar的長度必須大於6。
  15:         驗證成功!
  16: 當前字符長度:8
  17:         驗證成功!
  18:         驗證成功!
  19:         驗證成功!
  20:         驗證成功!
  21: 當前字符長度:10
  22:         驗證成功!
  23:         驗證失敗:
  24:                 屬性Bar的長度必須在6(不含6)與10(不含10)之間。
  25:         驗證成功!
  26:         驗證失敗:
  27:                 屬性Bar的長度必須小於10。
  28: 當前字符長度:12
  29:         驗證失敗:
  30:                 屬性Bar的長度必須在6(含6)與10(含10)之間。
  31:         驗證失敗:
  32:                 屬性Bar的長度必須在6(不含6)與10(不含10)之間。
  33:         驗證成功!
  34:         驗證失敗:
  35:                 屬性Bar的長度必須小於10。

三、為StringLengthValidator創建ValidatorElementAttribute

在這個“驗證框架”中,每一個非CompositeValidator不但可以單獨實施驗證,還可以作為ValidatorElement參與到CompositeValidator中,進行相對複雜的邏輯運算。作為ValidatorElement的Validator同樣通過自定義特性的方式應用到數據實體類型的目標屬性上,所以我們也需要StringLengthValidator創建相應的ValidatorElementAttribute,即。StringLengthValidatorElementAttribute和StringLengthValidatorAttribute處了MessageTemplate屬性替換成Name屬性之前,基本相同。

   1: public class StringLengthValidatorElementAttribute : ValidatorElementAttribute
   2: {
   3:     public int LowerBound { get;  set; }
   4:     public int UpperBound { get;  set; }
   5:     public RangeBoundaryType LowerBoundType { get;  set; }
   6:     public RangeBoundaryType UpperBoundType { get;  set; }
   7:  
   8:     public StringLengthValidatorElementAttribute(string name)
   9:         : base(name)
  10:     {
  11:         this.LowerBound = int.MinValue;
  12:         this.UpperBound = int.MaxValue;
  13:         this.LowerBoundType = RangeBoundaryType.Ignore;
  14:         this.UpperBoundType = RangeBoundaryType.Ignore;
  15:     }
  16:  
  17:     public override Validator CreateValidator()
  18:     {
  19:         return new StringLengthValidator(string.Empty, this.LowerBound, this.LowerBoundType, this.UpperBound, this.UpperBoundType) { Name = this.Name };
  20:     }
  21: }

那麼,如果我們要求Foo的Bar屬性的長度在如下的區間中:我們的Foo類型就可以定義成如下的形式:

   1: public class Foo
   2: {
   3:     private const string message4Rule = "屬性{PropertyName}的長度必須在如下的區間內:[2,4)U(6,10]U[12,14]。";
   4:  
   5:     [CompositeValidator(message4Rule, "V: between2And4 OR V: between6And10 OR V:between12And14")]
   6:     [StringLengthValidatorElement("between2And4", LowerBound = 2, LowerBoundType = RangeBoundaryType.Inclusive, UpperBound = 4, UpperBoundType = RangeBoundaryType.Exclusive)]
   7:     [StringLengthValidatorElement("between6And10", LowerBound = 6, LowerBoundType = RangeBoundaryType.Exclusive, UpperBound = 10, UpperBoundType = RangeBoundaryType.Inclusive)]
   8:     [StringLengthValidatorElement("between12And14", LowerBound = 12, LowerBoundType = RangeBoundaryType.Inclusive, UpperBound = 14, UpperBoundType = RangeBoundaryType.Inclusive)]
   9:     public string Bar { get; set; }
  10: }

練完收工:)

 

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


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

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

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