在自定义模型验证器 .NET Core 中格式化错误消息
Formatting error messages in custom model validator .NET Core
在 .NET Core 3.x 应用程序中,我有一个如下所示的模型,其中使用了自定义验证属性 [CustomValidator]
。 CustomValidator
返回的错误消息没有 $.
前缀,而内置 JSON 验证器 returns 带有前缀。我想让它们保持一致(是否始终使用 $.
前缀)。知道如何做到这一点吗?
示例模型:
public class Dto
{
public float Price {get; set;}
[CustomValidator] public int? EntityID {get; set;}
}
其中 [CustomValidator]
是一个自定义验证属性,它执行类似这样的操作
public class CustomValidatorAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var isValid = CheckSomething(...);
return isValid
? ValidationResult.Success
: new ValidationResult($"Invalid value for Entity");
}
}
我用
在 Controller.Action
中验证模型
if (!ModelState.IsValid) return BadRequest(ModelState);
对于输入:
{
"Price" : "abc"
}
它returns
{
...
"errors": {
"$.price": [
"The JSON value could not be converted to System.Single. Path: $.price | LineNumber: 7 | BytePositionInLine: 21."
]
}
}
而对于输入:
{
"Price" : 1.0,
"EntityID": -1,
}
它returns
{
...
"errors": {
"EntityID": [
"Invalid value for Entity"
]
}
}
我希望 errors
始终具有一致的 属性 名称,例如Price
和 EntityID
而不是 $.price
和 EntityID
$.
前缀是 JsonException
中包含的 json 路径的一部分。您可以在此处 SystemTextJsonInputFormatter.
默认 json 输入格式化程序的源代码中看到它
因此,从技术上讲,要在将路径传递给方法 ModelState.TryAddModelError
之前对其进行规范化,您当然需要以某种方式修改该路径。那样很复杂。相反,您可以为最终 ProblemDetails
结果配置自定义 InvalidModelStateResponseFactory
。尽管通过这种方式,您可能会在性能损失方面做出权衡,尤其是当验证错误频繁发生并且涉及很多错误时。但我认为这在现实中并不常见。这种方法的想法是修改 ModelState
,方法是尝试换出所有具有以 $.
为前缀的键的条目,并将它们替换为具有该键前缀的条目。
您可以通过配置 ApiBehaviorOptions
来配置该响应工厂,如下所示:
//inside Startup.ConfigureServices
services.Configure<ApiBehaviorOptions>(o => {
//we need to call this original factory inside our custom factory
var originalFactory = o.InvalidModelStateResponseFactory;
o.InvalidModelStateResponseFactory = context => {
//get all the keys prefixed with $. (which should be related to json errors)
var jsonPathKeys = context.ModelState.Keys.Where(e => e.StartsWith("$.")).ToList();
foreach(var key in jsonPathKeys)
{
var normalizedKey = key.Substring(2);
foreach (var error in context.ModelState[key].Errors)
{
if (error.Exception != null)
{
context.ModelState.TryAddModelException(normalizedKey, error.Exception);
}
context.ModelState.TryAddModelError(normalizedKey, error.ErrorMessage);
}
context.ModelState.Remove(key);
}
return originalFactory(context);
};
});
在 .NET Core 3.x 应用程序中,我有一个如下所示的模型,其中使用了自定义验证属性 [CustomValidator]
。 CustomValidator
返回的错误消息没有 $.
前缀,而内置 JSON 验证器 returns 带有前缀。我想让它们保持一致(是否始终使用 $.
前缀)。知道如何做到这一点吗?
示例模型:
public class Dto
{
public float Price {get; set;}
[CustomValidator] public int? EntityID {get; set;}
}
其中 [CustomValidator]
是一个自定义验证属性,它执行类似这样的操作
public class CustomValidatorAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var isValid = CheckSomething(...);
return isValid
? ValidationResult.Success
: new ValidationResult($"Invalid value for Entity");
}
}
我用
在Controller.Action
中验证模型
if (!ModelState.IsValid) return BadRequest(ModelState);
对于输入:
{
"Price" : "abc"
}
它returns
{
...
"errors": {
"$.price": [
"The JSON value could not be converted to System.Single. Path: $.price | LineNumber: 7 | BytePositionInLine: 21."
]
}
}
而对于输入:
{
"Price" : 1.0,
"EntityID": -1,
}
它returns
{
...
"errors": {
"EntityID": [
"Invalid value for Entity"
]
}
}
我希望 errors
始终具有一致的 属性 名称,例如Price
和 EntityID
而不是 $.price
和 EntityID
$.
前缀是 JsonException
中包含的 json 路径的一部分。您可以在此处 SystemTextJsonInputFormatter.
因此,从技术上讲,要在将路径传递给方法 ModelState.TryAddModelError
之前对其进行规范化,您当然需要以某种方式修改该路径。那样很复杂。相反,您可以为最终 ProblemDetails
结果配置自定义 InvalidModelStateResponseFactory
。尽管通过这种方式,您可能会在性能损失方面做出权衡,尤其是当验证错误频繁发生并且涉及很多错误时。但我认为这在现实中并不常见。这种方法的想法是修改 ModelState
,方法是尝试换出所有具有以 $.
为前缀的键的条目,并将它们替换为具有该键前缀的条目。
您可以通过配置 ApiBehaviorOptions
来配置该响应工厂,如下所示:
//inside Startup.ConfigureServices
services.Configure<ApiBehaviorOptions>(o => {
//we need to call this original factory inside our custom factory
var originalFactory = o.InvalidModelStateResponseFactory;
o.InvalidModelStateResponseFactory = context => {
//get all the keys prefixed with $. (which should be related to json errors)
var jsonPathKeys = context.ModelState.Keys.Where(e => e.StartsWith("$.")).ToList();
foreach(var key in jsonPathKeys)
{
var normalizedKey = key.Substring(2);
foreach (var error in context.ModelState[key].Errors)
{
if (error.Exception != null)
{
context.ModelState.TryAddModelException(normalizedKey, error.Exception);
}
context.ModelState.TryAddModelError(normalizedKey, error.ErrorMessage);
}
context.ModelState.Remove(key);
}
return originalFactory(context);
};
});