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


    昨天晚上突发奇想,弄了一个简易版的验证框架,用于进行数据实体的验证。目前仅仅实现基于属性的声明式的验证,即通过自定义特性(Custom Attribute)的方式将相应的Validator应用到对应的属性上,并设置相应的验证规则。本篇文章分上下两篇,上篇介绍如果来使用这个验证框架,《下篇》介绍背后的设计原理和具体实现。

    一、定义最简单的验证规则

    我们先看看一个最简单的验证规则如何应用到对应的实体类型上。在这里我们模拟一个有趣的场景:找对象,不论是找男朋友还是女朋友,还是不男不女的朋友,都具有一定的标准。在这里我们把这些标准表示成“验证规则”。为了简单,我们仅仅验证对方的年龄是否符合我们的要求,为此我定义了如下一个简单的Mate类型:

       1: public class Mate
       2: {
       3:     public int Age { get; set; }
       4:     public Mate(int age)
       5:     {
       6:         this.Age = age;
       7:     }
       8: }

    现在我们对年龄设置一个最基本的要求:对方必须是年满18岁的成年人。为此我通过一个GreaterThanValidatorAttribute特性将一个GreaterThanValidator应用到了Age属性上,并指定了GreaterThanValidator的下限18,同时设置了验证失败后相应错误信息。值得一提的是:指定的验证消息时一个消息模板,我们可以指定相应的站位符,比如{PropertyName}、{PropertyValue}、{Tag},它们分别表示对应属性的属性名、属性值和自定义的Tag(在具体的ValidatorAttribute特性中设置,在本例中设置的Tag为“年龄”)。这些都是一般的、被所有Validator支持的站位符,具体的Validator也可以定义自己的站位符,比如{LowerBound}就是专属于GreaterThanValidator特有。实际上还有其他一些站位符供你选择。

       1: public class Mate
       2: {
       3:    private const string greaterThanMsg = "通过属性{PropertyName}表示的{Tag}不能低于{LowerBound}周岁,当前{Tag}为{PropertyValue}周岁!";
       4:     
       5:     [GreaterThanValidator(greaterThanMsg, 18, Tag = "年龄")]   
       6:     public int Age { get; set; }
       7:  
       8:     public Mate(int age)
       9:     {
      10:         this.Age = age;
      11:     }
      12: }

    然后我定义了如下一个静态辅助方法Validate<T>进行对象的验证,如果验证成功,输出“验证成功”,否则输出“验证失败”,并输出格式化的出错消息。

       1: static void Validate<T>(T objectToValidate)
       2: {
       3:     IEnumerable<ValidationError> validationErrors;
       4:     if (!Validation.Validate(objectToValidate, out validationErrors))
       5:     {
       6:         Console.WriteLine("验证失败!");
       7:         foreach (var error in validationErrors)
       8:         {
       9:             Console.WriteLine("\t"+ error.Message);
      10:         }
      11:     }
      12:     else
      13:     {
      14:         Console.WriteLine("验证成功!");
      15:     }
      16: }

    上述的Validate<T>主要是调用了我静态类型Validation的Validate方法。该方法签名如下:布尔类型的返回之表示是否验证通过,输出参数为一个ValidationError对象集合,该对象表示具体出错信息。

       1: public static class Validation
       2: {
       3:     public static bool Validate(object value, out IEnumerable<ValidationError> validationErrors)
       4: }

    现在我们通过如下的代码对实例化的Mate对象进行两次验证,第一次设置的年龄为20(符合要求),第二次为16(没有成年,不符合要求)。输出的结果表明验证是按照我们期望的规则进行的。

       1: static void Main(string[] args)
       2: {
       3:     var mate = new Mate(20);
       4:     Validate<Mate>(mate);
       5:     mate.Age = 16;
       6:     Validate<Mate>(mate);
       7: }

    输出结果:

       1: 验证成功!
       2: 验证失败!
       3:         通过属性Age表示的年龄不能低于18周岁,当前年龄为16周岁!

    二、多条件验证规则

    在大部分情况下,验证规则不太可能通过单一的条件进行定义。就像你找女朋友的时候,不可能只要是对方年满18岁,你就照单全收一样。如果你需要添加多个验证条件,你只需要将相应的ValidatorAttribute特性应用到属性上即可。比如现在我们进一步修正我们的验证规则:年龄大于18周岁,并且小于30周岁。那么我们仅仅需要在Age属性上添加一个额外的LessThanValidatorAttribute特性即可,该特性将LessThanValidator应用到Age上面。具体代码如下所示,在LessThanValidatorAttribute设置了上限30,并制定了相应的验证消息模板。

       1: public class Mate
       2: {
       3:     private const string greaterThanMsg = "通过属性{PropertyName}表示的{Tag}不能低于{LowerBound}周岁,当前{Tag}为{PropertyValue}周岁!";
       4:     private const string lessThanMsg    = "通过属性{PropertyName}表示的{Tag}不能高于{UpperBound}周岁,当前{Tag}为{PropertyValue}周岁!";
       5:     [GreaterThanValidator(greaterThanMsg, 18, Tag = "年龄")]
       6:     [LessThanValidator(lessThanMsg, 30, Tag = "年龄")]
       7:     public int Age { get; set; }
       8:  
       9:     public Mate(int age)
      10:     {
      11:         this.Age = age;
      12:     }
      13: }

    现在我们稍微改动一下上面的程序,将用于第二次验证的Mate对象的Age属性设置成50

       1: static void Main(string[] args)
       2: {
       3:     var mate = new Mate(20);
       4:     Validate<Mate>(mate);
       5:  
       6:     mate.Age = 50;
       7:     Validate<Mate>(mate);       
       8: }

    执行程序,你将会得到如下的输出:

       1: 验证成功!
       2: 验证失败!
       3:         通过属性Age表示的年龄不能高于30周岁,当前年龄为50周岁!

    如果多个Validator应用到了同一个属性,在执行验证的时候都会被执行。最终返回的错误信息是验证失败的Validator返回的错误信息的集合。在此例中,返回的是LessThanValidator的ValidationError。但是有的时候我们希望获取一个完整的错误信息,比如:“通过属性Age表示的年龄必须在18到25周为之间,当前年龄为50周岁!”。在这种情况下,你需要一个特殊的Validator:AndCompositeValidator。

    三、试试AndCompositeValidator

    AndCompositeValidator属于一种特殊的Validator:CompositeValidator,这中Validator本身不进行具体的验证工作,而是根据具体的ValidatorElement验证后的结果,进行相应的逻辑运算,最终确定验证结果。AndCompositeValidator对应的逻辑运算就是:逻辑与,即所有ValidatorElement验证通过才被认为是本Validator验证通过。同样对于上面的验证规则,我们就可以通过应用AndCompositeValidatorAttribute特性,指定一个“友好的”验证消息。

       1: public class Mate
       2: {
       3:     private const string messageTemplate = "通过属性{PropertyName}表示的{Tag}必须在18到30周岁之间,当前{Tag}为{PropertyValue}周岁!";
       4:     [AndCompositeValidator(messageTemplate,"greaterThan18,lessThan30",Tag="年龄")]
       5:     [GreaterThanValidatorElement("greaterThan18", 18)]
       6:     [LessThanValidatorElement("lessThan30", 30)]
       7:     public int Age { get; set; }
       8:  
       9:     public Mate(int age)
      10:     {
      11:         this.Age = age;
      12:     }
      13: }

    虽然和上面采用相同的验证规则,但是这次我们采用的不是GreaterThanValidatorAttribute和LessThanValidatorAttribute,而是采用GreaterThanValidatorElementAttribute和LessThanValidatorElementAttribute。ValidatorElement服务于CompositeValidator,所有必须设置一个唯一的名称,在这里分别为:greaterThan18和lessThan30。

    在AndCompositeValidatorAttribute中,使用到的ValidatorElement的名称是“逗号”为分隔符连接的字符串指定到了构造函数的参数中(greaterThan18,lessThan30)。执行上面的程序,这次的出现的验证消息就是我们希望的了。

       1: 验证成功!
       2: 验证失败!
       3:         通过属性Age表示的年龄必须在18到30周岁之间,当前年龄为50周岁!

    四、有AndCompositeValidator,自然就有OrCompositeValidator

    AndCompositeValidator是基于“逻辑与”的CompositeValidator,自然就是基于“逻辑或”的OrCompositeValidator。OrCompositeValidator的用法和AndCompositeValidator完全一样。如果我们将验证规则改称这样:年龄要么大于30,要么小于18(这好像是“变态”的择偶标准)。那么我们的Mate类型就可以定义成如下的样子。

       1: public class Mate
       2: {
       3:     private const string messageTemplate = "通过属性{PropertyName}表示的{Tag}要么大于30,要么小于18,当前{Tag}为{PropertyValue}周岁!";
       4:     [OrCompositeValidator(messageTemplate,"greaterThan30,lessThan18",Tag="年龄")]
       5:     [GreaterThanValidatorElement("greaterThan30", 30)]
       6:     [LessThanValidatorElement("lessThan18", 18)]
       7:     public int Age { get; set; }
       8:  
       9:     public Mate(int age)
      10:     {
      11:         this.Age = age;
      12:     }
      13: }

    现在我们需要对我们的验证程序进行如下的修改。先将年龄设置成16(符合验证规则),然后设置成20(不符合验证规则)。执行程序,你也会得到期望的验证失败的错误消息。

       1: static void Main(string[] args)
       2: {
       3:     var mate = new Mate(16);
       4:     Validate<Mate>(mate);
       5:     mate.Age = 20;
       6:     Validate<Mate>(mate);
       7: }

    输出结果:

       1: 验证成功!
       2: 验证失败!
       3:         通过属性Age表示的年龄要么大于30,要么小于18,当前年龄为20周岁!

    五、让验证规则来得更加复杂点吧

    因为有了用于进行具体验证工作的Validator,比如上面用到的GreaterThanValidator和LessThanValidator,以及用于逻辑运算的CompositeValidator。我们的验证框架几乎能够表示所有的验证规则

    现在我们将验证规则进一步升级:年龄或者在18到25周岁之间,或者大于40周岁(现在很多女孩喜欢在成熟的老男人)。下面的Mate类型的定义反映了这样的验证规则。

       1: public class Mate
       2: {
       3:     private const string messageTemplate = "通过属性{PropertyName}表示的{Tag}或者在18到25周岁之间,或者大于40周岁,当前{Tag}为{PropertyValue}周岁!";
       4:  
       5:     [OrCompositeValidator(messageTemplate, "between18And25,greaterThan40",Tag="年龄")]
       6:     [AndCompositeValidatorElement("between18And25", "greaterThan18,lessThan25")]
       7:     [GreaterThanValidatorElement("greaterThan18", 18)]
       8:     [LessThanValidatorElement("lessThan25", 25)]
       9:     [GreaterThanValidatorElement("greaterThan40", 40)]
      10:     public int Age { get; set; }
      11:  
      12:     public Mate(int age)
      13:     {
      14:         this.Age = age;
      15:     }
      16: }

    对于最新的验证规则,最外层是“逻辑或”的关系,所以我们仅仅使用了一个唯一的OrOrCompositeValidator。组成该OrOrCompositeValidator的有两个分支,分别通过两个ValidatorElement表示。第一个ValidatorElement是一个AndCompositeValidatorElement(年龄在18到25周岁之间),后一个是GreaterThanValidatorElement(年龄高于40周岁)。而该AndCompositeValidatorElement又具有两个分支,通过一个GreaterThanValidatorElement(年龄大于18周岁)和一个LessThanValidatorElement(年龄小于25周岁)表示。

    现在我们修改验证程序,分别将用于验证的Mate对象的年龄设置成20(符合验证规则)、50(符合验证规则)、16(年纪太小,不符合验证规则)和30(年纪既不年轻,也不够成熟,不符合验证规则),输执行程序的输出反映了最终得验证结果。

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

    输出结果:

       1: 验证成功!
       2: 验证成功!
       3: 验证失败!
       4:         通过属性Age表示的年龄或者在18到25周岁之间,或者大于40周岁,当前年龄为16周岁!
       5: 验证失败!
       6:         通过属性Age表示的年龄或者在18到25周岁之间,或者大于40周岁,当前年龄为30周岁!

    六、对多验证规则的支持

    实体的验证应该是场景驱动的,对于同一中类型的对象,不同的场景决定不同的验证规则。对于“找对象”为例,不同的人具有不同的择偶标准,同一个人在不同的年龄阶段的择偶标准也不可能相同。所以,一个好的验证框架应该具有定义多中验证规则的能力。

    同样以上面的例子来说明,对于Mate类型,我希望为不同的人(比如张三和李四)定义不同的验证规则。其中张三喜欢18岁到25周岁的年轻小MM,李四则钟意35到40岁的中年妇女。那么我们可以将两种不同的验证规则通过如下的代码定义在Mate类型上。

       1: public class Mate
       2: {
       3:     private const string message4Zhangsan   = "通过属性{PropertyName}表示的{Tag}必须在18到25周岁之间,当前{Tag}为{PropertyValue}周岁!";
       4:     private const string message4Lisi       = "通过属性{PropertyName}表示的{Tag}必须在35到40周岁之间,当前{Tag}为{PropertyValue}周岁!";
       5:  
       6:     [AndCompositeValidator(message4Zhangsan, "greaterThan18,lessThan25", RuleName="Zhansan")]
       7:     [AndCompositeValidator(message4Lisi, "greaterThan35,lessThan40", RuleName = "Lisi")]
       8:     [GreaterThanValidatorElement("greaterThan18", 18)]
       9:     [GreaterThanValidatorElement("greaterThan35", 35)]
      10:     [LessThanValidatorElement("lessThan25", 25)]
      11:     [LessThanValidatorElement("lessThan40", 40)]
      12:     public int Age { get; set; }
      13:  
      14:     public Mate(int age)
      15:     {
      16:         this.Age = age;
      17:     }
      18: }

    通过上面的代码我们可以看到:在Age属性上应用了两个AndCompositeValidator,它的属性RuleName被设置成“zhansan”和“Lisi”,分别表示张三和李四的择偶标准。对于这种设置了RuleName的情况,在进行验证的时候需要具体的验证个规则命名,表明当前验证是基于那个具体的规则进行的。在静态的外观类Validation中,提供了另一个Validate方法重载,供你指定具体的验证规则名称。

       1: public static class Validation
       2: {    
       3:     public static bool Validate(object value, string ruleName, out IEnumerable<ValidationError> validationErrors)
       4:     {
       5:         //Others
       6:     }
       7: }

    为了演示方便,我们改动一下之前定义的辅助方法Validate,为它添加一个额外的表示验证规则名称的参数ruleName,该方法最终会调用上面的方法。

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

    那么我们通过上面的代码验证给定的Mate对象是否符合张三和李四的择偶标准,被用于验证的Mate对象的年龄分别为20和38周岁。从输出结果来看,第一个(年龄为20)是张三喜欢的,但是历史不喜欢;李四喜欢的是第二个(年龄为38),但是这个人不是张三中意的类型。

       1: static void Main(string[] args)
       2: {
       3:     var mate = new Mate(20);
       4:     Validate<Mate>(mate,"Zhansan");
       5:     Validate<Mate>(mate, "Lisi");
       6:  
       7:     mate.Age = 38;
       8:     Validate<Mate>(mate, "Zhansan");
       9:     Validate<Mate>(mate, "Lisi");           
      10: }

    输出结果:

       1: 验证成功!
       2: 验证失败!
       3:         通过属性Age表示的必须在35到40周岁之间,当前为20周岁!
       4: 验证失败!
       5:         通过属性Age表示的必须在18到25周岁之间,当前为38周岁!
       6: 验证成功!

    如果对这个验证框架的设计原理感兴趣,敬请关注《下篇》。要先睹为快的朋友,可以从这里下载源代码。

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

  • 相关阅读:
    卡特兰数
    hdu 1023 Train Problem II
    hdu 1022 Train Problem
    hdu 1021 Fibonacci Again 找规律
    java大数模板
    gcd
    object dection资源
    Rich feature hierarchies for accurate object detection and semantic segmentation(RCNN)
    softmax sigmoid
    凸优化
  • 原文地址:https://www.cnblogs.com/artech/p/Validation_Framework_Program.html
Copyright © 2020-2023  润新知