Handling Model Binding Errors when using [FromBody] in .NET Core 2.1

随声附和 提交于 2020-12-25 11:01:29

问题


I am trying to understand how I can intercept and handle model binding errors in .net core.

I want to do this:

    // POST api/values
    [HttpPost]
    public void Post([FromBody] Thing value)
    {
        if (!ModelState.IsValid)
        {
            // Handle Error Here
        }
    }

Where the Model for "Thing" is:

public class Thing
{
    public string Description { get; set; }
    public int Amount { get; set; }
}

However if I pass in an invalid amount like:

{ 
   "description" : "Cats",
   "amount" : 21.25
}

I get an error back like this:

{"amount":["Input string '21.25' is not a valid integer. Path 'amount', line 1, position 38."]}

Without the controller code ever being hit.

How can I customise the error being sent back? (as basically I need to wrap this serialisation error in a larger error object)


回答1:


So, I missed this before but I have found here:

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

That if you use the

[ApiController] 

attribute on your controller, it will automatically handle serialisation errors and provide the 400 response, equivalent to:

if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

You can turn this behaviour off in the Startup.cs like this:

services.AddMvc()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressModelStateInvalidFilter = true;
    });

If you are looking to customise the response, a better option is to use a InvalidModelStateResponseFactory, which is a delegate taking an ActionContext and returning an IActionResult which will be called to handle serialisation errors.

See this example:

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



回答2:


The framework uses Model Binders to map the request strings into a complex object, so my guess is that you will need to create a Custom Model Binder. Please refer Custom Model Binding in ASP.Net Core

But before that, an easier way to try would be to try Binder attributes in your models. BindRequired attribute adds a model state error if binding cannot occur. So you can modify your model as :

public class Thing 
{
    [BindRequired]
    public string Description {get;set;}

    [BindRequired]
    public int Amount {get;set;}
}

If that doesn't work for you, then you can try to create a custom model binder. An example from the article :

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

You might also want to look at Model Validation



来源:https://stackoverflow.com/questions/55100471/handling-model-binding-errors-when-using-frombody-in-net-core-2-1

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!