14

I get an inconsistent behaviour when asp.net core api validates objects and when I manually add model errors and invoke BadRequest(ModelState)

As an example, I have these 2 endpoints in my controller

[HttpPost]
public IActionResult Post(MyModel model)
{
    return Ok();
}

[HttpPost]
[Route("test")]
public IActionResult OtherPost()
{
    ModelState.AddModelError("field", "error");
    return BadRequest(ModelState);
}

and MyModel is:

public class MyModel
{
    [Required]
    [MinLength(10)]
    public string MyProperty { get; set; }
}

When I invoke the first endpoint with an empty body I don't need to validate ModelState because the framework is going to do it automatically and gives this response:

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

With the second controller I get this:

{"field":["error"]}

Am I using the wrong method to add errors to ModelState or it is an known problem?

nicecatch
  • 1,687
  • 2
  • 21
  • 38

3 Answers3

17

You could use

public IActionResult Post(SomeModel model)
{
    ModelState.AddModelError("key", "message");
    return ValidationProblem(ModelState);
}

This code produces similar response, without traceId only.
UPD1: For asp.net core 3.1 it returns traceId

SerjG
  • 3,325
  • 3
  • 30
  • 30
14
  1. If you prefer to validating the model state by yourself and expect a exactly same result as the failure message of ApiController binding, you could make it as below :
    public IActionResult Other2Post()
    {
        ModelState.AddModelError("field", "error");
        var problemDetails = new ValidationProblemDetails(ModelState)
        {
            Status = StatusCodes.Status400BadRequest,
        };

        var traceId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
        problemDetails.Extensions["traceId"] = traceId;

        var result = new BadRequestObjectResult(problemDetails);
        result.ContentTypes.Add("application/problem+json");
        result.ContentTypes.Add("application/problem+xml");
        return result;
    }
  1. Or if you don't need the traceId, you could simply return a BadRequest of ValidationProblemDetails:
    ModelState.AddModelError("field", "error");
    var problemDetails = new ValidationProblemDetails(ModelState)
    {
        Status = StatusCodes.Status400BadRequest,
    };
    return BadRequest(problemDetails);

Demo :

enter image description here

For more information, see the related source code here and here.

itminus
  • 23,772
  • 2
  • 53
  • 88
  • Thank you, that is what I was looking for – nicecatch Mar 22 '19 at 10:12
  • Thank you so much. Thank you for the links as well. This is making more sense. It was a shock to discover that my errors weren't being returned as errors and it made for some messy consumer side code for validation management. – Frinavale Jan 26 '23 at 18:04
2

ApiController performs automatic model state validation and returns a response in case of an error.

If you want similar behavior, you can disable the automatic validation and response:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<ApiBehaviorOptions>(options =>
    {
        options.SuppressModelStateInvalidFilter = true;
    });
}

See here for more info. Hope it helps!

John-Luke Laue
  • 3,736
  • 3
  • 32
  • 60