首页 > 解决方案 > 是否可以在不启用子属性隐式验证的情况下使用 FluentValidation 验证可枚举模型?

问题描述

给定以下模型、验证器和控制器(在 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();
    }
}

有没有一种方法可以在不启用我的启动类的情况下验证IEnumerable<Model>模型,如下所示?TheController.PostSomethingImplicitlyValidateChildProperties

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].Propertym[0].Property. 我一直无法找到删除 lambda 参数名称的方法,无论是使用WithName还是自定义DisplayNameResolver.

但是,这种方法感觉不对。我宁愿不必更改签名PostSomething或添加一些感觉像 kludge 的东西来删除 lambda 参数名称。

虽然我可以启用ImplicitlyValidateChildProperties它,但它可能会导致我需要进行的一些更改出现问题,所以我宁愿尽可能避免它。

标签: c#asp.net-coreasp.net-core-3.1fluentvalidation

解决方案


更新

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

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

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

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

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

ImplicitlyValidateChildPropertiesis时true,FluentValidation 将尝试为 type 查找验证器TElement,因此将发生对根集合模型的元素的验证。但是,FluentValidation 也将验证TElement匹配验证器在哪里注册并且ImplicitlyValidateChildPropertiestrue. 在我的情况下,我希望对集合元素进行验证,但我不希望对每个元素的子属性进行自动验证,因此不适合启用对子属性的隐式验证。

覆盖RuleForEach属性名称

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

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

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

概括

可用的选项有:

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

推荐阅读