FluentValidation:仅验证一个属性是否已设置

7

我正在努力实现一个验证器,用于验证一个类中只有一个属性被设置。

假设我们有以下类:

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 应为null;如果设置了SecondOptionalProperty,则FirstOptionalPropertyThirdOptionalProperty应为null,以此类推。

换句话说:如果设置了IEnumerable属性中的任何一个,则其他IEnumerable应为null。

有没有关于实现此类验证器的提示/想法?我想到的唯一方法是编写一些When规则的代码块,但这种编写代码的方式容易出错并且结果看起来很丑陋。


那么基本上归结为只有3个可选属性中的1个可以设置为 NotBeNull?我理解的对吗? - Mong Zhu
@MongZhu 如果设置了其中一个属性,则其他属性应为null。 - Anon Anon
3个回答

6
你可以利用Must重载来访问整个类对象,以便对其他属性进行属性验证。有关更多详细信息,请参见FluentValidation多属性规则
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的错误消息但他们没有提供该消息,他们可能会有点困惑。

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


谢谢您提供的解决方案,我会采用它。 - Anon Anon
1
你不能使用异或运算来检查是否仅有一个属性被设置,除非有奇数个属性被设置...这里有一个相关的问题 https://dev59.com/aGUp5IYBdhLWcg3w_rgl - Jason C
@JasonC 谢谢你的回复。我当时发布这篇文章时认为我已经进行了测试,但是阅读了你提供的链接并进行了更多的调查后,发现我错了。我已经编辑了我的帖子,并给你点了个赞。 - gunr2171
你可以通过检查计数是否小于1来轻松消除第一个空值检查....Count(x => x == true) < 1; - Jason C

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,那么就意味着不止一个可选属性被实例化了。

1
曾经考虑过这个问题,但不知道如何在验证器中访问类的实例。 感谢@gunr2171的答案,现在我知道如何获取类的实例了。 由于我的IEnumerable属性在我的情况下是不同类型的(我在问题中将它们简化为相同类型,但在实际情况下它们并不相同),所以我想我会结合你们两个的答案。非常感谢你们的帮助。 - Anon Anon
1
@AnonAnon 如果你把它们结合起来,当你解决了问题后,看到结果会很好。然后请发布它 :) 强制属性的排除标准是可变的,如果您有两个具有例如“DateTime”类型的属性,则也可以使用其名称;) - Mong Zhu

2
我会为此使用一个辅助函数。
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 */
}

这种方法的缺点是值会被装箱。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接