【问题标题】:Is it possible to validate an enumerable model using FluentValidation without enabling implicit validation of child properties?是否可以在不启用子属性隐式验证的情况下使用 FluentValidation 验证可枚举模型?
【发布时间】:2020-11-13 15:34:54
【问题描述】:

给定以下模型、验证器和控制器(在 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&lt;Model&gt; 模型,而无需在我的启动类中启用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,但它可能会导致我需要进行一些更改,因此我希望尽可能避免它。

【问题讨论】:

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


【解决方案1】:

更新

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

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

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

模型绑定将ICollection&lt;TElement&gt;IEnumerable&lt;TElement&gt;IList&lt;TElement&gt; 反序列化为List&lt;TElement&gt;(参见CollectionModelBinder.CreateEmptyCollection)。

ImplicitlyValidateChildPropertiesfalse 时,FluentValidation 将仅尝试查找与操作方法参数的绑定类型匹配的验证器,该类型为List&lt;TElement&gt; 或在我的示例中为List&lt;Model&gt;。因此,无需更改TheController.PostSomething 的签名,但验证器必须派生自AbstractValidator&lt;List&lt;TElement&gt;&gt;

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. 重新设计模型(尽管这是否可行显然取决于各种其他因素)。

【讨论】:

    猜你喜欢
    • 2011-10-05
    • 1970-01-01
    • 2017-12-14
    • 2020-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-19
    • 2021-10-03
    相关资源
    最近更新 更多