16

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?

4
  • I believe the framework might be calling AddModelError (string key, Exception exception, Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata metadata) instead. Try adding that in your code instead of just a text error. Commented Mar 21, 2019 at 21:43
  • what should their values be? Commented Mar 21, 2019 at 22:39
  • 1
    I was wrong. See github.com/aspnet/AspNetCore/issues/6077 a detailed discussion. Commented Mar 22, 2019 at 0:20
  • @Andrea is your controller inheriting from Controller or ApiController? Commented Mar 22, 2019 at 0:31

3 Answers 3

19

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

Sign up to request clarification or add additional context in comments.

3 Comments

This is the easiest and best response.
Worked quite nicely for me as well.
In ASP.NET Core 6, it's sufficient with return ValidationProblem();, it will use ControllerBase.ModelState by default.
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.

2 Comments

Thank you, that is what I was looking for
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.
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!

2 Comments

You could disable model state validation by just removing [ApiController] attribute.
After 6 years it saved my day!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.