前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >采用一个自创的"验证框架"实现对数据实体的验证[改进篇]

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

作者头像
蒋金楠
发布2018-02-08 13:34:23
9180
发布2018-02-08 13:34:23
举报
文章被收录于专栏:大内老A大内老A大内老A

自《编程篇》和《设计篇》发布以来,收到了一些反馈。尤其是园友双鱼座提到.NET 3.5下的System.ComponentModel.DataAnnotations命名空间具有相似的实现,并且通过“表达式”的方式实现了CompositeValidator的服务逻辑判断的功能。为此,我对这个“验证框架”进行了相应的改进,让CompositeValidator具有了解析“验证表达式”的能力。为了让大家对此改进又一个深刻的认识,我们来对比之下对于同一个验证规则,改进前后有何不同。[源代码从这里下载]

一、改进前如何使用CompositeValidator?

改进前的CompositeValidator仅仅提供对两种基本逻辑运算(“析取”和“或取”)的支持,他们分别通过两个具体的CompositeValidator实现,即AndCompositeValidator和OrCompositeValidator。那么对于一些相对复杂的验证规则,应用在属性上的ValidatorAttribute或者ValidatorElementAttribute有可能需要很多,将使我们的代码显得非常臃肿。通过采用《编程篇》中的“找对象”的例子,如果对年龄具有这样的要求:年龄要么在18到25周岁之间,要么在40岁到50周岁之间,对于这段不算太复杂的验证规则,我们需要在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: greaterThan18 AND V: lessThan25) OR (V: greaterThan40 AND V: lessThan50),不但直观而且简洁。在这段表达式中通过“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安全模块的Authorization表达式的解析方法。由于逻辑稍微有点复杂,有兴趣的朋友可以分析一下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: }

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2010-10-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、在新的CompositeValidator中使用表达式来定义验证规则
  • 三、最新的CompositeValidator作了哪些改变?
  • 四、最终的验证逻辑变得简单
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档