是否可以使用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();
}
}

有没有一种方法可以验证TheController.PostSomethingIEnumerable<Model>模型,而不在我的启动类中启用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将只尝试查找与操作方法参数的绑定类型匹配的验证器,该类型将为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. 重新设计模型(尽管这是否是一个可行的选择显然取决于其他各种因素(

最新更新