如何使用 Fluent Validator return RuleForEach 验证消息中的特定 属性?

How to return a specific property in the message of a RuleForEach validation, using Fluent Validator?

假设我有这样的测试class:

public class TestClass
{
    public Properties[] TestProperties { get; set; }
    public Guid Id { get; set; }

    public TestClass(Properties[] testProperties)
    {
        Id = Guid.NewGuid();
        TestProperties = testProperties;
    }
}

还有一个属性class如下:

 public class Properties
{
    public Guid Id { get; set; }
    public string Name { get; set; }

    public Properties(string name)
    {
        Name = name;
        Id = Guid.NewGuid();
    }
}

我需要验证 TestProperties 数组中我的属性 Name 的 none 是否为空,如下所示:

public class TestValidator : AbstractValidator<TestClass>
{
    public TestValidator()
    {
        RuleForEach(x => x.TestProperties)
            .Must(y => y.Name != string.Empty && y.Name != null)
            .WithMessage("TestPropertie at {CollectionIndex}, can't be null or empty");
    }
}

但是我不想 return 在验证消息中 return 失败 属性 的位置,我想 return 它是 Id,我该怎么做那么?

是的,使用默认验证器可以将对象中的其他 属性 值注入到消息中。

This can be done by using the overload of WithMessage that takes a lambda expression, and then passing the values to string.Format or by using string interpolation.

Source

有几种方法可以做到这一点。首先,根据您当前使用 Must:

的实现
public class TestClassValidator : AbstractValidator<TestClass>
{
    public TestClassValidator()
    {
        RuleForEach(x => x.TestProperties)
            .Must(y => !string.IsNullOrEmpty(y.Name))
            .WithMessage((testClass, testProperty) => $"TestProperty {testProperty.Id} name can't be null or empty");
    }
}

我尽可能避免使用 Must,如果您坚持使用内置验证器,您更有可能开箱即用地进行客户端验证(如果您正在使用它在网络应用程序中)。使用 ChildRules 可以让您使用内置验证器并获得使用流畅界面的好处:

public class TestClassValidator : AbstractValidator<TestClass>
{
    public TestClassValidator()
    {
        RuleForEach(x => x.TestProperties)
            .ChildRules(testProperties =>
            {
                testProperties.RuleFor(testProperty => testProperty.Name)
                    .NotNull()
                    .NotEmpty()
                    .WithMessage(testProperty => $"TestProperty {testProperty.Id} name can't be null or empty");
            });
    }
}

ChildRules doco

我已经将 verbosity/alignment 的 NotNull() 验证器包含在自定义错误消息中,但是不需要它,因为 NotEmpty() 将涵盖 null 或空的情况。

最后,如果是我,我可能会为 Properties 类型创建一个单独的验证器(应该是 Property 吗?)并使用 SetValidator 来包含它。拆分验证问题,一次定义类型的验证并使规则可重用,并使验证器更易于测试。我不打算在这里讨论这个问题,因为这超出了这个问题的范围,但下面的链接给出了如何做的例子。

子验证器 doco(SetValidator 用法)here and here

可以找到上述工作示例,包括测试 here

我的处理方式略有不同,因为我想要一个可重用性更高的解决方案。 (我正在以类似的方式验证许多不同的 类)。将消息标识放在带有 Must<> 的扩展名中会将您与类型联系起来,并可能导致复制和粘贴。相反,我将一个 Func 作为参数传递给验证,returns 一个标识字符串并让调用者决定如何标识被验证的对象。

    public static IRuleBuilderOptions<T, string> IsValidStringEnumAllowNullable<T>(this IRuleBuilder<T, string> ruleBuilder, IList<string> validValues, Func<T,string> identifierLookup)
    {
        return ruleBuilder.Must((rootObject, testValue, context) =>
        {
            context.MessageFormatter.AppendArgument("AllowableValues", string.Join(", ", validValues));
            context.MessageFormatter.AppendArgument("Identifier", identifierLookup(rootObject));
            return string.IsNullOrEmpty(testValue) || validValues.Contains(testValue, StringComparer.Ordinal);

        }).WithMessage("{Identifier}{PropertyName} with value {PropertyValue} must be one of the allowable values: {AllowableValues}, or null or empty string");
    }

然后是调用代码,其中我告诉特定的验证消息 'how' 以识别消息传递的对象:

base.RuleForEach(rq => rq.Thingies).ChildRules(x =>
{
    x.RuleFor(f => f.MyProperty).IsValidStringEnumAllowNullable(ValidationStrings.AnArrayOfAllowedValues, f => $"Thing[{f.Id}] ");
});

这段代码的结果是

Thing[1234] My Property with value asdf must be one of the allowable values: Value1, ValidValue2, Somethingelse, or null or empty string