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

问题描述 投票:0回答:4

我正在努力为一个类实现验证器,其中只应设置一个属性。

假设我们有以下课程:

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
-
SecondOptionalProperty
ThirdOptionalProperty
应为空,如果设置了
SecondOptionalProperty
-
FirstOptionalProperty
ThirdOptionalProperty
应为空,等等。

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

关于为此类类型实现验证器有什么技巧/想法吗?我唯一想到的就是编写大量

When
规则,但这种编写代码的方式很容易出错,而且结果看起来很难看。

c# fluentvalidation
4个回答
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的错误消息,但他们没有提供该消息。

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


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);

基本上,如果

info
null
,则实例化了超过 1 个可选属性


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 */
}

0
投票

这是@gunr2171解决方案的修改示例

它避免了他们提到的缺点,同时仍然使用 FluentValidation。

public class SomeClassValidator : AbstractValidator<SomeClass>
{
    private const string OneOptionalPropertyMessage = "Only one of FirstOptionalProperty, SecondOptionalProperty, or ThirdOptionalProperty can be set.";

    public SomeClassValidator()
    {
        // This ends up passing the entire SomeClass object to the function twice...
        // It's kind of a hack but it works.
        RuleFor(x => x)
            .Must(OptionalPropertiesAreValid)
            .WithName(nameof(SomeClass))
            .WithErrorCode($"{nameof(OptionalPropertiesAreValid)}Validator")
            .WithMessage(OneOptionalPropertyMessage);
    }

    // this "break out" method only works because all of the optional properties
    // in the class are of the OptionalPropertiesAreValidsame type. You'll need to move the logic back
    // inline in the Must if that's not the case.
    private bool OptionalPropertiesAreValid(SomeClass obj, SomeClass redundantObj)
    {
        // "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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.