是否可以在不启用子属性隐式验证的情况下使用 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.PostSomething
的 IEnumerable<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
)。
当 ImplicitlyValidateChildProperties
为 false
时,FluentValidation 将仅尝试查找与 action 方法参数的绑定类型相匹配的验证器,这将是 List<TElement>
或者在我的示例中 List<Model>
。因此,无需更改 TheController.PostSomething
的签名,但验证器必须派生自 AbstractValidator<List<TElement>>
.
当 ImplicitlyValidateChildProperties
为 true
时,FluentValidation 将尝试为类型 TElement
查找验证器,因此将对根集合模型的元素进行验证。但是,FluentValidation 还将验证 TElement
的子属性,其中注册了匹配的验证器并且 ImplicitlyValidateChildProperties
是 true
。在我的例子中,我希望对集合元素进行验证,但我不希望对每个元素的子属性进行自动验证,因此不适合启用子属性的隐式验证。
覆盖 RuleForEach
属性 名称
不幸的是,尝试使用 WithName
或 OverridePropertyName
并传递 string.Empty
来删除 lambda 参数名称失败,因为 null 或空的 属性 名称总是被覆盖(在 v9.3.0 中,请参阅 CollectionPropertyRule.InvokePropertyValidator
)。这对集合属性有意义,RuleForEach
似乎主要是为集合属性设计的,但不是为根集合设计的。
通过显式验证,似乎有两种方法可以从验证错误中删除 lambda 参数名称,这两种方法都有点麻烦。
- 覆盖
Validate
并重写 属性 名称。
- 实施
IValidatorInterceptor
并重写AfterMvcValidation
中的属性名称。
总结
可用的选项是:
- 将
ImplicitlyValidateChildProperties
设置为 true
并确保设计适用于自动验证子属性。
- 使用显式验证并覆盖
Validate
或使用验证器拦截器在验证后重写 属性 名称。
- 重新设计模型(尽管这是否可行显然取决于其他各种因素)。
给定以下模型、验证器和控制器(在 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.PostSomething
的 IEnumerable<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
)。
当 ImplicitlyValidateChildProperties
为 false
时,FluentValidation 将仅尝试查找与 action 方法参数的绑定类型相匹配的验证器,这将是 List<TElement>
或者在我的示例中 List<Model>
。因此,无需更改 TheController.PostSomething
的签名,但验证器必须派生自 AbstractValidator<List<TElement>>
.
当 ImplicitlyValidateChildProperties
为 true
时,FluentValidation 将尝试为类型 TElement
查找验证器,因此将对根集合模型的元素进行验证。但是,FluentValidation 还将验证 TElement
的子属性,其中注册了匹配的验证器并且 ImplicitlyValidateChildProperties
是 true
。在我的例子中,我希望对集合元素进行验证,但我不希望对每个元素的子属性进行自动验证,因此不适合启用子属性的隐式验证。
覆盖 RuleForEach
属性 名称
不幸的是,尝试使用 WithName
或 OverridePropertyName
并传递 string.Empty
来删除 lambda 参数名称失败,因为 null 或空的 属性 名称总是被覆盖(在 v9.3.0 中,请参阅 CollectionPropertyRule.InvokePropertyValidator
)。这对集合属性有意义,RuleForEach
似乎主要是为集合属性设计的,但不是为根集合设计的。
通过显式验证,似乎有两种方法可以从验证错误中删除 lambda 参数名称,这两种方法都有点麻烦。
- 覆盖
Validate
并重写 属性 名称。 - 实施
IValidatorInterceptor
并重写AfterMvcValidation
中的属性名称。
总结
可用的选项是:
- 将
ImplicitlyValidateChildProperties
设置为true
并确保设计适用于自动验证子属性。 - 使用显式验证并覆盖
Validate
或使用验证器拦截器在验证后重写 属性 名称。 - 重新设计模型(尽管这是否可行显然取决于其他各种因素)。