FluentValidation:验证仅设置了一个属性

Posted

技术标签:

【中文标题】FluentValidation:验证仅设置了一个属性【英文标题】:FluentValidation: validate only one property is set 【发布时间】:2021-06-21 08:25:04 【问题描述】:

我正在为一个类实现一个验证器而苦苦挣扎,其中应该只设置一个属性。

假设我们有以下类:

public class SomeClass

    public DateTime SomeDate get; set;
    public IEnumerable<int> FirstOptionalProperty get; set;
    public IEnumerable<int> SecondOptionalProperty get; set;
    public IEnumerable<int> ThirdOptionalProperty get; set;

这个类有一个强制属性 - SomeDate。其他属性是可选的,只能设置一个,例如如果设置了FirstOptionalProperty - SecondOptionalPropertyThirdOptionalProperty 应该为空,如果设置了SecondOptionalProperty - FirstOptionalPropertyThirdOptionalProperty 应该为空等等。

换句话说:如果设置了 IEnumerable 属性之一 - 其他 IEnumerables 应该为空。

关于为这种类型的类实现验证器的任何提示/想法?我想出的唯一办法就是编写大块的When 规则,但是这种编写代码的方式容易出错,而且结果看起来很丑。

【问题讨论】:

所以基本上可以归结为NotBeNull 只允许 3 个可选属性中的 1 个?我没听错吗? @MongZhu 如果设置了这些属性之一 - 其他属性应该为空 【参考方案1】:

您可以利用 Must 重载来访问整个类对象,以便您可以针对其他属性进行属性验证。详情请见FluentValidation rule for multiple properties。

public class SomeClassValidator : AbstractValidator<SomeClass>

    private const string OneOptionalPropertyMessage = "Only one of FirstOptionalProperty, SecondOptionalProperty, or ThirdOptionalProperty can be set.";

    public SomeClassValidator()
    
        RuleFor(x => x.FirstOptionalProperty)
            .Must(OptionalPropertiesAreValid)
            .WithMessage(OneOptionalPropertyMessage);

        RuleFor(x => x.SecondOptionalProperty)
            .Must(OptionalPropertiesAreValid)
            .WithMessage(OneOptionalPropertyMessage);

        RuleFor(x => x.ThirdOptionalProperty)
            .Must(OptionalPropertiesAreValid)
            .WithMessage(OneOptionalPropertyMessage);
    

    // this "break out" method only works because all of the optional properties
    // in the class are of the same type. You'll need to move the logic back
    // inline in the Must if that's not the case.
    private bool OptionalPropertiesAreValid(SomeClass obj, IEnumerable<int> prop)
    
        // "obj" is the important parameter here - it's the class instance.
        // not going to use "prop" parameter.

        // if they are all null, that's fine
        if (obj.FirstOptionalProperty is null && 
            obj.SecondOptionalProperty is null && 
            obj.ThirdOptionalProperty is null)
        
            return true;
        

        // else, check that exactly 1 of them is not null
        return new [] 
         
            obj.FirstOptionalProperty is not null,
            obj.SecondOptionalProperty is not null, 
            obj.ThirdOptionalProperty is not null
        
        .Count(x => x == true) == 1;
        // yes, the "== true" is not needed, I think it looks better
    

您可以调整检查功能。按照目前的情况,如果您设置了 2 个或更多可选属性,它们都会抛出错误。这可能适合您的需求,也可能不适合您的需求。

您也可以只为第一个可选属性而不是所有属性创建RuleFor,因为所有属性都将执行相同的 IsValid 代码并返回相同的消息,如果出现以下情况,您的用户可能会有点困惑他们收到 OptionalProperty1 的错误消息,但他们没有提供那个。

这种方法的缺点是您需要在编译时知道所有属性是什么(以便您可以为其编写代码),并且如果您添加/删除可选条目,则需要维护此验证器。这个缺点对你来说可能很重要,也可能不重要。

【讨论】:

感谢您的解决方案,我会使用它 只有在设置了奇数个属性时,才能使用 XOR 检查是否设置了单个属性...这是一个相关问题***.com/questions/14888174/… @JasonC 谢谢。我以为我在发布此内容时进行了测试,但是阅读您的链接并进行更多调查证明我错了。我已经编辑了我的帖子,我也会给你一个赞成票。 您可以通过检查计数小于 1 来轻松消除第一次空检查....Count(x =&gt; x == true) &lt; 1;【参考方案2】:

我想到的一件事是在这里使用反射:

SomeClass someClass = new SomeClass

    SomeDate = DateTime.Now,
    FirstOptionalProperty = new List<int>(),
    //SecondOptionalProperty = new List<int>() // releasing this breakes the test
;

var info = typeof(SomeClass).GetProperties()
                            .SingleOrDefault(x =>
                                 x.PropertyType != typeof(DateTime) &&
                                 x.GetValue(someClass) != null);

基本上如果infonull 则实例化了多个可选属性

【讨论】:

想过,但不知道如何在验证器中访问类的实例。感谢@gunr2171 的回答,现在我知道如何获取类实例。而且由于在我的情况下 IEnumerable 属性具有不同的类型(我在问题中将它们简化为相同的类型,但在实际情况下它们并不相同)我想我会结合你的两个答案。感谢您的帮助,谢谢。 @AnonAnon 如果您将两者结合起来,当您解决问题后会很高兴看到结果。然后请发布:) 强制属性的排除标准是可变的,如果您有 2 个属性,您也可以使用它的名称,例如DateTime 类型 ;)【参考方案3】:

我会为此使用辅助函数。

private static bool OnlyOneNotNull(params object[] properties) =>
    properties.Count(p => p is not null) == 1;

你会这样使用它。

SomeClass s = new SomeClass();
/* ... */

if(!OnlyOneNotNull(s.FirstOptionalProperty, s.SecondOptionalProperty, s.ThirdOptionalProperty))

    /* handle error case */

【讨论】:

以上是关于FluentValidation:验证仅设置了一个属性的主要内容,如果未能解决你的问题,请参考以下文章

FluentValidation - 仅当值不为空时检查值是表达式

使用 FluentValidation 如何在控制器中使用 validationContext 进行测试

FluentValidation 和服务器+客户端远程验证器

FluentValidation:一个非常受欢迎的,用于构建强类型验证规则的.NET 库

使用 FluentValidation 在一条规则中进行多重验证

模型验证组件 FluentValidation