ASP.NET Core – How to manually validate a model in a controller

Manually validating a model can mean a few different things. It depends on what you’re trying to do exactly. Are you trying to validate a model object against its validation attributes? Use TryValidateModel(). Are you trying to do validation logic manually (instead of using validation attributes)? You can add errors to ModelState in that case.

Here’s an example of using TryValidateModel() and returning ValidationProblem():

[ApiController]
[Route("[controller]")]
public class MovieController : ControllerBase
{
	[HttpPost()]
	public IActionResult Post()
	{
		var movie = GetMovieFromBody(Request.Body);

		if (!TryValidateModel(movie))
		{
			return ValidationProblem();
		}

		//process movie

		return Ok();
	}
	
	//rest of controller
}
Code language: C# (cs)

Internally, TryValidateModel() calls ObjectValidator.Validate() with a bunch of parameters and returns a bool depending on the outcome. It populates ModelState with validation errors. When you return ValidationProblem(), the framework uses the errors in ModelState to generate a problem details response (400-Bad Request), which looks like this:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "00-9f27411aa3becf2c5c5ff16bdebaf9d1-884befd590994113-00",
    "errors": {
        "Year": [
            "The field Year must be between 1900 and 2022."
        ]
    }
}Code language: JSON / JSON with Comments (json)

Please note, this is specific to ASP.NET Core. If you want to do model validation manually outside of an ASP.NET Core project, read Manually validate objects that have model validation attributes.

Add validation errors to ModelState manually

Let’s say you want to do the validation logic manually in the controller. You can add validation errors to ModelState and then return ValidationProblem(), like this:

[HttpPost()]
public IActionResult Post(Movie movie)
{
	//Step 1 - Manually validate all the fields you want
	if (movie.Year > DateTime.UtcNow.Year)
	{
		ModelState.TryAddModelError(nameof(movie.Year), "Year must not be in the future");
	}

	//Step 2 - Return validation problem
	if (!ModelState.IsValid)
		return ValidationProblem();
		
	//process movie

	return Ok();
}
Code language: C# (cs)

I suggest separating this into two steps, as shown above, instead of trying to return ValidationProblem() for every single condition. If you have many fields to validate, you’ll probably want to extract the logic into utility methods.

This manually added validation error looks like this in the problem details response:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "00-c215ddabcd316c299f66617335a0c4f1-9c245be6cfa7fca4-00",
    "errors": {
        "Year": [
            "Year must not be in the future"
        ]
    }
}
Code language: JSON / JSON with Comments (json)

Also, to be clear, this is not an all-or-nothing scenario where you either use validation attributes or not. You can do a combo of using validation attributes AND do manual validation logic. Use whatever combination makes sense in your scenario.

Handling automatic validation results manually

There are two reasons why you might decide to handle validation results manually:

  • You want to return a custom error (instead of the “problem details” response).
  • You want to try to fix the data problem.

First, if you’re using the [ApiController] attribute on your controller, then you’ll need to disable the automatic validation error handling by setting SuppressModelStateInvalid=true, like this:

builder.Services.Configure<ApiBehaviorOptions>(options =>
{
    options.SuppressModelStateInvalidFilter = true;
});
Code language: C# (cs)

Now you can check ModelState.IsValid to see if validation failed and then return a custom error response:

[HttpPost()]
public IActionResult Post(Movie movie)
{
	if (!ModelState.IsValid)
	{
		return BadRequest("Invalid data");
	}
	
	//rest of method
}
Code language: C# (cs)

Note: To do this for action methods, set ApiBehaviorOptions.InvalidModelStateResponseFactory instead.

If you want, you can try fixing the data problem and re-validating the model object. Here’s a simplified example:

[HttpPost()]
public IActionResult Post(Movie movie)
{
	if (!ModelState.IsValid)
	{
		ModelState.Clear();

		movie.Year = 2000;

		if (!TryValidateModel(movie))
		{
			return ValidationProblem();
		}
	}
	
	//rest of method
}
Code language: C# (cs)

Leave a Comment