在 .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

所以,我之前错过了这个,但我在这里找到了:

https://docs.microsoft.com/en-us/aspnet/core/web-api/index?view=aspnetcore-2.2#automatic-http-400-responses

如果你使用

[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);
    }
});