Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core model binding behaviour switch

During some heavy refactorings of an API it seems I changed something effecting the model binding / model validation behaviour.

I tried to investigate the changes I can't figure out what the change caused.

I have a MyApiController inheriting from ControllerBase. MyApiController has a post method receiving a request model (created with the default API template without HTTPS giving you the typical ValuesController).

  using Microsoft.AspNetCore.Mvc;
  using Microsoft.Extensions.Logging;
  using Newtonsoft.Json;
  using System;
  using System.Threading.Tasks;

  namespace XperimentModelBinding.Controllers
  {

    [ApiController]
    [Route("api/[controller]")]
    public class MyApiController : ControllerBase
    {

      public ILogger<MyApiController> Logger { get; }

      public MyApiController(ILogger<MyApiController> logger)
      {
        Logger = logger ?? throw new ArgumentNullException(nameof(logger));
      }


      [HttpPost()]
      public async Task<IActionResult> PostModel([FromBody] MyCreateRequestModel request)
      {

        Logger.LogInformation("PostModel: " + JsonConvert.SerializeObject(request, Formatting.None));

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

        return Ok();

      }
    }
  }

The model I use is:

  using System.ComponentModel.DataAnnotations;

  namespace XperimentModelBinding
  {

    public class MyCreateRequestModel
    {

      [Required]
      [StringLength(10)]
      public string Name { get; set; }

      [Required]
      [Range(1, 5)]
      public int Value { get; set; }

    }

  }

Starting it up and setting a breakpoint at the logger.

Testing with postman:

Test 1:

  {
    "Name": "1234567890",
    "Value": 1
  }

Breakpoint gets hit, returns 200 OK (as expected).

Test 2:

  {
    "Name": null,
    "Value": 1
  }

breakpoint doesn't get hit and the returned model is:

  {
    "errors": {
        "Name": [
            "The Name field is required."
        ]
    },
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "80000006-0000-ff00-b63f-84710c7967bb"
  }

The expected result would be that the method is called, the linebreak is hit and so far I had the response model (seems only containing the errors):

{
    "Name": [
        "The Name field is required."
    ]
}

What changed is: given a request with an invalid model my method got called and I checked with ModelState.IsValid if there were errors. That was great because I created my custom response model in that way.

Now my method doesn't get called anymore and the model binding returns directly its own model.

What changed that my method isn't called anymore?

like image 642
monty Avatar asked Oct 19 '25 09:10

monty


2 Answers

Its connected to [ApiController] attribute from link

Web API controllers don't have to check ModelState.IsValid if they have the > [ApiController] attribute. In that case, an automatic HTTP 400 response containing issue details is returned when model state is invalid. For more information, see Automatic HTTP 400 responses.

like image 193
Pribina Avatar answered Oct 22 '25 00:10

Pribina


You may want to use ConfigureApiBehaviorOptions inside your services.AddMvc() in Startup.cs to setup a custom error message to an invalid model request.

services.AddMvc()
    .ConfigureApiBehaviorOptions(options => {
        options.InvalidModelStateResponseFactory = actionContext =>
        {
            var modelState = actionContext.ModelState.Values;
            return new BadRequestObjectResult(new ErrorResult(modelState));
        };
    });
});

And define your ErrorResult class as you want, for example:

public class ErrorResult
{
    public int code { get; set; }
    public string message { get; set; }

    public ErrorResult()
    {
    }

    public ErrorResult(ModelStateDictionary.ValueEnumerable modelState)
    {
        // This will take the error message coming directly from modelState
        foreach (var value in modelState)
        {
            if (value.Errors.Count > 0)
            {
                code = 900; // Or use a code handler or whatever
                message = value.Errors.FirstOrDefault().ErrorMessage;
                break;
            }
        }
    }
}
like image 36
ALFA Avatar answered Oct 21 '25 22:10

ALFA



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!