65.9K
CodeProject 正在变化。 阅读更多。
Home

对象级验证框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (27投票s)

2006年11月10日

5分钟阅读

viewsIcon

114771

downloadIcon

508

提供一个基本的/高级的对象级别的验证框架。

引言

在所有层面上,对象都需要基本的验证。ASP.NET 在表示层提供了此服务,但在业务层没有正式的方法来实现这一点。

问题

我一直在大量使用 O/R 映射器,并意识到每次手动编写验证代码都非常令人厌烦。这是一种非常重复和冗余的代码,简直无聊透顶。 Castle's Active Record(非常好)内置了一个验证系统。它基于 Ruby (on Rails) 的 ActiveRecord 模式,该模式具有更强大的验证系统。我发现的问题是,验证框架与底层实体模型紧密耦合。无法在其他地方利用该框架。

初步鸣谢

我为此工作了一段时间,最近发现了另一个 验证框架 并阅读了源代码。它有一些好主意,我选择性地将它们融入了这个框架(CompareValidator 是主要的)。他提到了他读过的一篇 文章,碰巧我也读过,并从中获得了一些想法。

ValidationManager

实际上只有一个主要类,即 ValidationManager。这个类代表了验证器的中央存储库。它有两个 static 方法:一个用于向类型添加 Validator,另一个用于验证实例。例如:

Validator val = RequiredValidator.CreateValidator<TestClass>("TestProperty");
ValidationManager.AddGlobalValidator<TestClass>(val);
TestClass tc = new TestClass();
if(ValidationManager.IsValid(tc))
   MessageBox.Show("WOO HOO");

此代码现在已为所有新实例注册了类型为 TestClass 的验证器。此外,已创建的 TestClass 的所有实例都会收到添加通知,并刷新它们的验证器缓存。(由于实例化 ValidationManager 实例所涉及的开销,此验证方法不是首选。)

嗯……什么是验证器缓存?验证器缓存是实例类型的全局验证器缓存的实例引用。这意味着我们也可以添加实例验证器,而不是全局验证器。例如:

TestClass tc = new TestClass();
ValidationManager vm = new ValidationManager(tc);

Validator val = RequiredValidator.CreateValidator<TestClass>("TestProperty");
vm.AddValidator(val);

此代码现在已为 tc 实例注册了一个验证器。TestClass 的其他实例没有此必需的验证器。您还会注意到 ValidationManager 在构造函数参数中接受一个对象。此 ValidationManager 实例与传入的对象实例相关联。例如:

TestClass tc = new TestClass();
ValidationManager vm = new ValidationManager(tc);
if(vm.IsValid())
   MessageBox.Show("WOO HOO");

基于属性的验证

该框架虽然可以通过编程方式进行操作,但通过属性使用起来要容易得多。例如:

public class TestClass
{
    private string testProperty;

    [RequiredValidator]
    [LengthValidator(1,10)]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
}
...


TestClass tc = new TestClass();
ValidationManager vm = new ValidationManager(tc);

if(vm.IsValid())
   MessageBox.Show("WOO HOO");

实例化 ValidationManager 时,它会通过反射扫描 TestClass 以查找 ValidatorAttribute,并将它们添加到全局验证器缓存中。它每个类型只执行一次,因此开销只发生一次。

每个验证器属性都包含通用的 ErrorMessage Order 属性。这些可以作为命名参数指定,但不是必需的。ErrorMessage 有一个默认值,Order 指示如果有多个验证器时要处理它们的顺序。(侧边栏:顺序仅适用于属性,而不适用于实例验证器)。

验证器

目前有 7 个验证器。每个验证器都有一个对应的属性可以使用。此外,每个属性都有静态方法,可以方便地以编程方式创建自身。

  1. Compare Validator

    适用于实现 IComparable 的类型。

    通过使用指定的比较运算符将实例值与常量值进行比较来测试。

    ...
    [CompareValidator(ComparisonOperator.GreaterThan,0)]
    public int TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    ...
    
  2. Custom Validator

    适用于任何类型。

    将类型中存在的某个方法名作为参数。该方法必须符合 Predicate<T> 委托签名。

    ...
    [CustomValidator("TestProperty cannot contain a period.","NoPeriodTest")]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    
    private bool NoPeriodTest(string value)
    {
        return !value.Contains(".");
    }
    ...
    
  3. Length Validator

    适用于 System.String 和实现 ICollection 的类型。

    通过确保字符串的长度或集合的计数在长度限制范围内来测试。

    ...
    [LengthValidator(1,10)]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    ...
    
  4. NotEmpty Validator

    适用于实现 IEnumerable 的类型。

    通过确保至少有一个元素可枚举来测试。

    ...
    [NotEmptyValidator]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    ...
    
  5. Range Validator

    适用于实现 IComparable 的类型。

    通过检查值是否包含在最小值和最大值之间来测试。(包含边界值)。

    ...
            [RangeValidator(1,10)]
            public int TestProperty
            {
                get { return this.testProperty; }
                set { this.testProperty = value; }
            }
    ...
    
  6. Regular Expression Validator

    适用于字符串。

    通过检查值是否匹配正则表达式来测试。

    ...
    [RegexValidator(@"\d+")]
    public string TestProperty
    {
        get { return this.testProperty; }
        set { this.testProperty = value; }
    }
    ...
    
  7. Required Validator

    适用于任何类型。但是,值类型(如 int)将始终通过验证。可空类型按预期工作。

    测试 null。因此,虽然它适用于值类型,但它将始终返回 true。可空类型行为正常。

    ...
            [RequiredValidator]
            public string TestProperty
            {
                get { return this.testProperty; }
                set { this.testProperty = value; }
            }
    ...
    

待办事项

我认为这个库已经相当完整了。有 25 个使用 NUnit 框架的单元测试。(我绝不是一个单元测试编写者,所以请不要嘲笑它们。)我确实希望做的就是引入另一种方式来向框架注册验证器。我正在尝试通过 app/web 配置文件来实现。这将完全将业务逻辑验证与域模型解耦。

此外,我不确定该库是否是线程安全的。我认为它是,因为底层字典对象是线程安全的,但我不是这方面的专家,将推迟给他人判断。

最后,我还在犹豫是否允许在运行时从类型和/或实例中删除验证器。我没有要求此功能,但也许其他人会发现它很有用。

有趣的属性

在为该框架工作了一段时间后,我后来意识到该框架与控制反转(Inversion of Control)相关。例如,我们可以传入一个 ValidationManager 和任何对象的实例,注册一些验证器,并确保其有效性。例如:

ArrayList list = new ArrayList();
ValidationManager vm = new ValidationManager(list);
vm.AddValidator(NotEmptyValidator.CreateValidator(typeof(ArrayList),"Count"));
Assert.IsFalse(vm.IsValid());
list.Add(2);
Assert.IsTrue(vm.IsValid());

显然,这是一个微不足道的例子,但说明了这一点。

结论

我们创建了一个正式的、可扩展且易于使用的验证框架。希望您喜欢这篇文章,但更希望您喜欢使用这个库。如果您进行了代码更改/增强/错误修复,请及时告知我。

© . All rights reserved.