验证策略

时间:2011-07-06 16:02:31

标签: c# design-patterns

我有一个包含许多类的业务模型,这个模型中的一些逻辑实体由许多不同的类组成(父子孙。)在这些不同的类中,我定义了不变的约束,例如,根的复合应该具有Code的值。

我目前每个类都实现了这样的接口......

public interface IValidatable
{
    IEnumerable<ValidationError> GetErrors(string path);
}

如果未设置Code,父级将添加验证错误,然后对每个子级执行GetErrors,这反过来会在每个孙子上调用GetErrors。

现在我需要为不同的操作验证不同的约束,例如

  1. 应始终检查某些约束,因为它们是不变的
  2. 当我想在根目录上执行操作X时,应该检查一些约束。
  3. 执行操作Y时可能会检查一些其他约束。
  4. 我考虑在GetErrors方法中添加一个“Reason”参数,但由于某种原因,我无法完全理解这一点并不合适。我还考虑过创建一个访问者并有一个具体的实现来验证OperationX和另一个针对OperationY但不喜欢这个,因为一些约束检查需要多个操作但不是所有操作(例如,OperationX + OperationY需要一个Date但不是OperationZ),我不想复制检查的代码。

    任何建议都将不胜感激。

8 个答案:

答案 0 :(得分:4)

这里存在绝缘问题,因为您的类负责进行自己的验证,但验证的性质取决于您正在进行的操作类型。这意味着类需要知道可以对它们执行的操作类型,这会在类和使用它们的操作之间创建相当紧密的耦合。

一种可能的设计是创建一组并行的类:

public interface IValidate<T>
{
    IEnumerable<ValidationError> GetErrors(T instance, string path);
}

public sealed class InvariantEntityValidator : IValidate<Entity>
{
    public IEnumerable<ValidationError> GetErrors(Entity entity, string path)
    {
        //Do simple (invariant) validation...
    }
}

public sealed class ComplexEntityValidator : IValidate<Entity>
{
    public IEnumerable<ValidationError> GetErrors(Entity entity, string path)
    {
        var validator = new InvariantEntityValidator();
        foreach (var error in validator.GetErrors(entity, path))
            yield return error;

        //Do additional validation for this complex case
    }
}

您仍然需要解决将验证类与要验证的各种类关联的方式。听起来这应该以某种方式发生在操作级别,因为这是您知道需要进行何种类型的验证的地方。如果不更好地理解你的架构,很难说。

答案 1 :(得分:3)

我会做一种基于属性的验证:

public class Entity
{
    [Required, MaxStringLength(50)]
    public string Property1 { get; set; }  

    [Between(5, 20)]
    public int Property2 { get; set; }

    [ValidateAlways, Between(0, 5)]
    public int SomeOtherProperty { get; set; }      

    [Requires("Property1, Property2")]
    public void OperationX()
    {
    }
}

传递给Requires - 属性的每个属性都需要有效才能执行操作。

具有ValidateAlways - 属性的属性必须始终有效 - 无论何种操作。

在我的伪代码Property1中,Property2 SomeOtherProperty必须有效才能执行OperationX

当然,您必须在Requires-attribute中添加一个选项,以检查子项的验证属性。但是,如果没有看到一些示例代码,我无法建议如何做到这一点。

也许是这样的:

[Requires("Property1, Property2, Child2: Property3")]

如果需要,您还可以使用lambda表达式而不是字符串(Example)来获取强类型属性指针。

答案 2 :(得分:2)

我建议使用Fluent Validation For .Net库。这个库允许您非常轻松灵活地设置验证器,如果您需要针对不同操作进行不同的验证,您可以非常轻松地使用适用于该特定操作(并将其更改)的验证。

答案 3 :(得分:1)

我出于完全相同的原因使用了Sptring.NET的验证引擎 - 它允许您使用条件验证器。您只需定义规则 - 应用什么验证以及在什么条件下应用Spring,其余部分。好处是您的业务逻辑不再受到验证接口的污染

您可以在documentation at springframework.net中找到更多信息。我将复制他们的文档样本,以显示它的样子:

  

<v:condition test="StartingFrom.Date >= DateTime.Today" when="StartingFrom.Date != DateTime.MinValue">

     

<v:message id="error.departureDate.inThePast" providers="departureDateErrors, validationSummary"/>

     

</v:condition>

     

在此示例中,将Trip对象的StartingFrom属性进行比较以查看它是否晚于当前日期,即DateTime,但仅在设置了日期时(StartingFrom.Date的初始值设置为DateTime.MinValue) )。

     

条件验证器可被视为“所有验证器的母亲”。您可以使用它来实现几乎任何可以通过使用其他验证器类型实现的内容,但在某些情况下,测试表达式可能非常复杂,这就是为什么您应该使用更具体的验证器类型(如果可能)。但是,如果您需要检查特定值是否属于特定范围,或者执行类似的测试,条件验证器仍然是您最好的选择,因为这些条件相当容易编写。

答案 4 :(得分:1)

如果您正在使用.Net 4.0,则可以使用Code Contracts来控制其中的一部分。

答案 5 :(得分:0)

尝试从上到下阅读本文,我从中获得了很多想法。

http://codebetter.com/jeremymiller/2007/06/13/build-your-own-cab-part-9-domain-centric-validation-with-the-notification-pattern/

这是基于属性的域验证,带有通知,将这些验证包装到更高层。

答案 6 :(得分:0)

我会将变体验证逻辑分开,可能与您提到的访问者分开。通过将验证与类分开,您将使您的操作与数据分开,这确实有助于保持清洁。

也可以这样思考 - 如果你将验证和操作与数据类混合在一起,想一想从现在起一年后会发生什么,当你进行增强时需要添加一项新的行动。如果每个操作的验证规则和操作逻辑是分开的,那么它主要只是一个“添加” - 您创建一个新操作,以及一个新的验证访问者。另一方面,如果你必须返回并在每个数据类中触摸很多“if operation == x”逻辑,那么你会有一些额外的回归错误/等风险。

答案 7 :(得分:0)

我依赖于已在System.ComponentModel.DataAnnotations Namespace&#39例如,在ASP.NET MVC中使用。

这个提供了不显眼的方式来应用验证规则(使用属性或实现IValidatableObject)和设施来验证规则。

Scott Allen 在精彩文章&#39; Manual Validation with Data Annotations&#39;中描述了这种方式。