采用一个自创的"验证框架"实现对数据实体的验证[扩展篇]
关于“验证框架”,先后推出了《编程篇》、《设计篇》和《改进篇》,本不打算再写《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: }
练完收工:)
采用一个自创的"验证框架"实现对数据实体的验证[编程篇]
采用一个自创的"验证框架"实现对数据实体的验证[设计篇]
采用一个自创的"验证框架"实现对数据实体的验证[改进篇]
采用一个自创的"验证框架"实现对数据实体的验证[扩展篇]