是否可以在不启用子属性隐式验证的情况下使用 FluentValidation 验证可枚举模型?

Is it possible to validate an enumerable model using FluentValidation without enabling implicit validation of child properties?

给定以下模型、验证器和控制器(在 ASP.NET Core 3.1 中):

public sealed class Model
{
    public string Property { get; set; }
}

public sealed class ModelValidator : AbstractValidator<Model>
{
    public ModelValidator()
    {
        RuleFor(m => m.Property).NotEmpty();
    }
}

[ApiController]
[Route("[controller]")]
public sealed class TheController : ControllerBase
{
    [HttpPost]
    public IActionResult PostSomething(IEnumerable<Model> model)
    {
        // Do something
        return Ok();
    }
}

有没有一种方法可以验证 TheController.PostSomethingIEnumerable<Model> 模型,而无需在我的启动 class 中启用 ImplicitlyValidateChildProperties,如下所示?

services.AddControllers()
    .AddFluentValidation(c => c.ImplicitlyValidateChildProperties = true);

到目前为止,我无法找到一种在不设置 ImplicitlyValidateChildProperties = true 的情况下成功验证可枚举模型的方法。

更新:当更改PostSomething的签名,并添加和注册一个额外的验证器(如下)验证确实发生。

[HttpPost]
public IActionResult PostSomething(List<Model> model)
{
    // Do something
    return Ok();
}

public sealed class ListOfModelValidator : AbstractValidator<List<Model>>
{
    public ListOfModelValidator()
    {
        RuleForEach(m => m).SetValidator(new ModelValidator());
    }
}

然而,这感觉不对。它还会导致返回的 ValidationProblemDetails 响应出现问题。验证错误的元素的索引将以 lambda 参数的名称为前缀,因此 [0].Property 将是 m[0].Property。我一直无法找到删除 lambda 参数名称的方法,使用 WithName 或自定义 DisplayNameResolver.

然而,这种做法感觉不对。我宁愿不必更改 PostSomething 的签名或添加一些感觉像杂乱无章的东西来删除 lambda 参数名称。

虽然我可以只启用 ImplicitlyValidateChildProperties,但它有可能导致我需要进行的一些更改出现问题,因此我希望尽可能避免它。

更新

从 FluentValidation 9.4.0 版开始,ImplicitlyValidateRootCollectionElements 选项可用,这将启用集合类型模型的验证,而无需同时启用子属性的隐式验证。它可以像隐式子模型验证一样启用,例如:

services.AddMvc().AddFluentValidation(fv => {
   fv.ImplicitlyValidateRootCollectionElements = true;
});

模型绑定和隐式子 属性 验证

模型绑定将 ICollection<TElement>IEnumerable<TElement>IList<TElement> 反序列化为 List<TElement>(请参阅 CollectionModelBinder.CreateEmptyCollection)。

ImplicitlyValidateChildPropertiesfalse 时,FluentValidation 将仅尝试查找与 action 方法参数的绑定类型相匹配的验证器,这将是 List<TElement> 或者在我的示例中 List<Model>。因此,无需更改 TheController.PostSomething 的签名,但验证器必须派生自 AbstractValidator<List<TElement>>.

ImplicitlyValidateChildPropertiestrue 时,FluentValidation 将尝试为类型 TElement 查找验证器,因此将对根集合模型的元素进行验证。但是,FluentValidation 还将验证 TElement 的子属性,其中注册了匹配的验证器并且 ImplicitlyValidateChildPropertiestrue。在我的例子中,我希望对集合元素进行验证,但我不希望对每个元素的子属性进行自动验证,因此不适合启用子属性的隐式验证。

覆盖 RuleForEach 属性 名称

不幸的是,尝试使用 WithNameOverridePropertyName 并传递 string.Empty 来删除 lambda 参数名称失败,因为 null 或空的 属性 名称总是被覆盖(在 v9.3.0 中,请参阅 CollectionPropertyRule.InvokePropertyValidator)。这对集合属性有意义,RuleForEach 似乎主要是为集合属性设计的,但不是为根集合设计的。

通过显式验证,似乎有两种方法可以从验证错误中删除 lambda 参数名称,这两种方法都有点麻烦。

  1. 覆盖 Validate 并重写 属性 名称。
  2. 实施IValidatorInterceptor并重写AfterMvcValidation中的属性名称。

总结

可用的选项是:

  1. ImplicitlyValidateChildProperties 设置为 true 并确保设计适用于自动验证子属性。
  2. 使用显式验证并覆盖 Validate 或使用验证器拦截器在验证后重写 属性 名称。
  3. 重新设计模型(尽管这是否可行显然取决于其他各种因素)。