采用一个自创的"验证框架"实现对数据实体的验证[扩展篇]

关于“验证框架”,先后推出了《编程篇》、《设计篇》《改进篇》,本不打算再写《XXX篇》的。但是今天收到两个园友的短消息,想了解一下如何定义自己的验证规则。这实际上涉及到对该“验证框架”的扩展,即如何自定义Validator和对应的ValidatorAttribute与ValidatorElementAttribute。为了让本系列看起来完整,通过《扩展篇》进行收尾。本篇我们写一个简单的Validator,用于验证字符串类型属性成员的长度是否符合要求(实际上我是直接借鉴了EnterLib中VAB下的同名Validator的设计)。

一、创建一个自定义Validator:StringLengthValidator

StringLengthValidator数据实体类型的字符串属性进行校验,确保它的长度符合要求(比如小于或者等于数据库中该列的最大长度)。这是一个非常简单的验证逻辑,只需验证大于(或者大于等于)执行的长度下限,小于(或者小于等于)指定的长度上限就可以了。由于有时候只要求被验证的字符串大(小)于指定的下(上)限,有时候被验证的字符可以包括上(下)限,有时则不可以。为了代表这样的比较方式,我定义如下RangeBoundaryType枚举。Ignore、Iclusive和Exclusive分别表示忽略、包含和不包含指定的上(下)限。

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

StringLengthValidator的整个定义如下所示,定义在Validate方法中的验证逻辑简单得令人发指,应该无需多做介绍吧。唯一值得一提的是,基于StringLengthValidator的验证消息模板添加了两个占位符{LowerBound}和{UpperBound},最终被被设置的上下限长度所代替。如果你愿意,还可以将{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。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个验证规则要求该属性表示的字符长度:在6到10之间(包含6和10);在6和10之间(不包含6和10);大于6;小于10。

   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属性的字符长度先后设置成4、6、8、10和12。从执行程序得到的输出可以看出我们的代码执行的验证工作是正确的。

   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。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属性的长度在如下的区间中:[2,4)U(6,10]U[12,14],我们的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: }

练完收工:)

采用一个自创的"验证框架"实现对数据实体的验证[编程篇] 采用一个自创的"验证框架"实现对数据实体的验证[设计篇] 采用一个自创的"验证框架"实现对数据实体的验证[改进篇] 采用一个自创的"验证框架"实现对数据实体的验证[扩展篇]

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏熊二哥

让我们一起写出更有效的CSharp代码吧,少年们!

周末空闲,选读了一下一本很不错的C#语言使用的书,特此记载下便于对项目代码进行重构和优化时查看。 Standing On Shoulders of Giant...

1855
来自专栏静晴轩

lua表排序

Lua作为一种很强大且轻量级脚本语言的存在,对于掌握其几乎无所不能的Table(其实就是一个Key Value的数据结构,它很像Javascript中的Obje...

43411
来自专栏小樱的经验随笔

Codeforces 714A Meeting of Old Friends

A. Meeting of Old Friends time limit per test:1 second memory limit per test:256...

37610
来自专栏Spark学习技巧

Flink DataSet编程指南-demo演示及注意事项

Flink中的DataStream程序是对数据流进行转换的常规程序(例如,过滤,更新状态,定义窗口,聚合)。数据流的最初的源可以从各种来源(例如,消息队列,套接...

3.7K12
来自专栏.net core新时代

再谈Newtonsoft.Json高级用法

  上一篇Newtonsoft.Json高级用法发布以后收到挺多回复的,本篇将分享几点挺有用的知识点和最近项目中用到的一个新点进行说明,做为对上篇文章的补充。 ...

2068
来自专栏技术博客

Asp.Net Web API 2第十三课——ASP.NET Web API中的JSON和XML序列化

阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.h...

1503
来自专栏大内老A

WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)

大部分的系统都是以数据为中心的(Data Central),功能的实现表现在对相关数据的正确处理。而数据本身,是有效信息的载体,在不同的环境具有不同的表示。一个...

3528
来自专栏大内老A

ASP.NET MVC Model元数据及其定制: Model元数据的定制

在《上篇》我们已经提到过了,Model元数据的定制是通过在作为Model的数据类型极其属性成员上应用相应的特性来实现,这些用于声明式元数据定义的特性大都定义在S...

3364
来自专栏PHP在线

PHP7新特性介绍

文内容根据PHP发布时的 new files 而来,链接地址 : PHP 7 new 特性一览 Added??operator Added <=> opera...

3656
来自专栏日常分享

Java 循环队列的实现

  队列(Queue)是限定只能在一端插入、另一端删除的线性表。允许删除的一端叫做队头(front),允许插入的一端叫做队尾(rear),没有元素的队列称为“空...

1943

扫码关注云+社区

领取腾讯云代金券