在 .NET Core 2.1 中使用 [FromBody] 时处理模型绑定错误
Handling Model Binding Errors when using [FromBody] in .NET Core 2.1
我正在尝试了解如何拦截和处理 .net Core 中的模型绑定错误。
我想这样做:
// POST api/values
[HttpPost]
public void Post([FromBody] Thing value)
{
if (!ModelState.IsValid)
{
// Handle Error Here
}
}
"Thing" 的模型是:
public class Thing
{
public string Description { get; set; }
public int Amount { get; set; }
}
但是,如果我输入的金额无效,例如:
{
"description" : "Cats",
"amount" : 21.25
}
我收到这样的错误:
{"amount":["Input string '21.25' is not a valid integer. Path 'amount', line 1, position 38."]}
没有命中控制器代码。
如何自定义发回的错误? (基本上我需要将这个序列化错误包装在一个更大的错误对象中)
该框架使用模型绑定器将请求字符串映射到一个复杂的对象中,所以我猜您需要创建一个自定义模型绑定器。请参考Custom Model Binding in ASP.Net Core
但在此之前,更简单的尝试方法是在模型中尝试 Binder 属性。 BindRequired 属性在绑定无法发生时添加模型状态错误。因此,您可以将模型修改为:
public class Thing
{
[BindRequired]
public string Description {get;set;}
[BindRequired]
public int Amount {get;set;}
}
如果这对您不起作用,那么您可以尝试创建自定义模型活页夹。文章中的示例:
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
public class AuthorEntityBinder : IModelBinder
{
private readonly AppDbContext _db;
public AuthorEntityBinder(AppDbContext db)
{
_db = db;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult =
bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName,
valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
int id = 0;
if (!int.TryParse(value, out id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName,
"Author Id must be an integer.");
return Task.CompletedTask;
}
// Model will be null if not found, including for
// out of range id values (0, -3, etc.)
var model = _db.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
您可能还想看看 Model Validation
所以,我之前错过了这个,但我在这里找到了:
如果你使用
[ApiController]
控制器上的属性,它会自动处理序列化错误并提供400响应,相当于:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
您可以像这样在 Startup.cs 中关闭此行为:
services.AddMvc()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
如果您希望自定义响应,更好的选择是使用 InvalidModelStateResponseFactory,它是一个采用 ActionContext 并返回将被调用以处理序列化错误的 IActionResult 的委托。
看这个例子:
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = actionContext =>
{
var errors = actionContext.ModelState
.Where(e => e.Value.Errors.Count > 0)
.Select(e => new Error
{
Name = e.Key,
Message = e.Value.Errors.First().ErrorMessage
}).ToArray();
return new BadRequestObjectResult(errors);
}
});
我正在尝试了解如何拦截和处理 .net Core 中的模型绑定错误。
我想这样做:
// POST api/values
[HttpPost]
public void Post([FromBody] Thing value)
{
if (!ModelState.IsValid)
{
// Handle Error Here
}
}
"Thing" 的模型是:
public class Thing
{
public string Description { get; set; }
public int Amount { get; set; }
}
但是,如果我输入的金额无效,例如:
{
"description" : "Cats",
"amount" : 21.25
}
我收到这样的错误:
{"amount":["Input string '21.25' is not a valid integer. Path 'amount', line 1, position 38."]}
没有命中控制器代码。
如何自定义发回的错误? (基本上我需要将这个序列化错误包装在一个更大的错误对象中)
该框架使用模型绑定器将请求字符串映射到一个复杂的对象中,所以我猜您需要创建一个自定义模型绑定器。请参考Custom Model Binding in ASP.Net Core
但在此之前,更简单的尝试方法是在模型中尝试 Binder 属性。 BindRequired 属性在绑定无法发生时添加模型状态错误。因此,您可以将模型修改为:
public class Thing
{
[BindRequired]
public string Description {get;set;}
[BindRequired]
public int Amount {get;set;}
}
如果这对您不起作用,那么您可以尝试创建自定义模型活页夹。文章中的示例:
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
public class AuthorEntityBinder : IModelBinder
{
private readonly AppDbContext _db;
public AuthorEntityBinder(AppDbContext db)
{
_db = db;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult =
bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName,
valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
int id = 0;
if (!int.TryParse(value, out id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName,
"Author Id must be an integer.");
return Task.CompletedTask;
}
// Model will be null if not found, including for
// out of range id values (0, -3, etc.)
var model = _db.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
您可能还想看看 Model Validation
所以,我之前错过了这个,但我在这里找到了:
如果你使用
[ApiController]
控制器上的属性,它会自动处理序列化错误并提供400响应,相当于:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
您可以像这样在 Startup.cs 中关闭此行为:
services.AddMvc()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
如果您希望自定义响应,更好的选择是使用 InvalidModelStateResponseFactory,它是一个采用 ActionContext 并返回将被调用以处理序列化错误的 IActionResult 的委托。
看这个例子:
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = actionContext =>
{
var errors = actionContext.ModelState
.Where(e => e.Value.Errors.Count > 0)
.Select(e => new Error
{
Name = e.Key,
Message = e.Value.Errors.First().ErrorMessage
}).ToArray();
return new BadRequestObjectResult(errors);
}
});