【问题标题】:FluentValidation: validate only one property is setFluentValidation:验证仅设置了一个属性
【发布时间】: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 如果设置了这些属性之一 - 其他属性应该为空

标签: c# fluentvalidation


【解决方案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 检查是否设置了单个属性...这是一个相关问题stackoverflow.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 */
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多