How to Customize Automatic HTTP 400 Error Response in ASP.NET Core – Windows ASP.NET Core Hosting 2024 | Review and Comparison

How to Customize Automatic HTTP 400 Error Response in ASP.NET Core

The behavioral options for the APIs can be enabled by annotating the controllers with the ApiController attribute in ASP.NET Core 2.1 or higher. Additionally included in these behavioral options are automatic HTTP 400 responses.

We’ll look at how to change the ASP.NET Core Web API’s default error response in this post.

Default error response

The ValuesController.cs file will be present in the project if you are starting from scratch with a default ASP.NET Core web API project. If not, build an action method to a parameter in a Controller and test the automatic HTTP 400 responses.

You would need to annotate the controller with the [ApiController] attribute if you were building your own custom API. The 400 default responses won’t function otherwise.

For the time being, I’ll use the built-in ValuesController. The Get action with the parameter id passed in is already available.

// GET api/values/5
[HttpGet(“{id}”)]
public ActionResult Get(int id)
{
    return “value”;
}
Let’s attempt to use Postman to pass in a string as the id parameter for the Get action.
This is the error response that the API returns by default.
Because ASP.NET Core performed the ModelState checking for us because our controller has the [ApiController] attribute on top of it, you may have noticed that we omitted it from our Get action method.

Customizing the error response

To modify the error response we need to make use of the InvalidModelStateResponseFactory property.

A delegate called InvalidModelStateResponseFactory will call the actions marked with an ApiControllerAttribute in order to transform an invalid ModelStateDictionary into an IActionResult.

For HTTP 400 responses, the ValidationProblemDetails class is the default response type. Thus, we will define our own error messages and create a custom class that inherits from the ValidationProblemDetails class.

CustomBadRequest class

Here is our CustomBadRequest class which assigns error properties in the constructor.

public class CustomBadRequest : ValidationProblemDetails
{
    public CustomBadRequest(ActionContext context)
    {
        Title = “Invalid arguments to the API”;
        Detail = “The inputs supplied to the API are invalid”;
        Status = 400;
        ConstructErrorMessages(context);
        Type = context.HttpContext.TraceIdentifier;
    }

    private void ConstructErrorMessages(ActionContext context)
    {
        foreach (var keyModelStatePair in context.ModelState)
        {
            var key = keyModelStatePair.Key;
            var errors = keyModelStatePair.Value.Errors;
            if (errors != null && errors.Count > 0)
            {
                if (errors.Count == 1)
                {
                    var errorMessage = GetErrorMessage(errors[0]);
                    Errors.Add(key, new[] { errorMessage });
                }
                else
                {
                    var errorMessages = new string[errors.Count];
                    for (var i = 0; i < errors.Count; i++)
                    {
                        errorMessages[i] = GetErrorMessage(errors[i]);
                    }

                    Errors.Add(key, errorMessages);
                }
            }
        }
    }

    string GetErrorMessage(ModelError error)
    {
        return string.IsNullOrEmpty(error.ErrorMessage) ?
            “The input was not valid.” :
            error.ErrorMessage;
    }
}

Since ActionContext gives us additional information about the action, I’m using it as a constructor argument. Since I’m using the TraceIdentifier from the HttpContext, I’ve used the ActionContext. Route data, HttpContext, ModelState, and ActionDescriptor will all be present in the Action context.

For this bad request customization, at the very least, you could pass in just the model state in the action context. You have the final say.

Plugging the CustomBadRequest in the configuration

There are two ways we can set up our newly created CustomBadRequest in the Startup.cs class’s Configure method.

using ConfigureApiBehaviorOptions off AddMvc()

ConfigureApiBehaviorOptions is an extension method on IMvcBuilder interface. Any method that returns an IMvcBuilder can call the ConfigureApiBehaviorOptions method.

public static IMvcBuilder ConfigureApiBehaviorOptions(this IMvcBuilder builder, Action setupAction);

The AddMvc() returns an IMvcBuilder and we will plug our custom bad request here.

services.AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
        .ConfigureApiBehaviorOptions(options =>
        {
            options.InvalidModelStateResponseFactory = context =>
            {
                var problems = new CustomBadRequest(context);

                return new BadRequestObjectResult(problems);
            };
        });

using the generic Configure method

Since we are not chaining the configuration here, this will be convenient. Here, we’ll only be utilizing the generic configure method.

services.Configure(a =>
{
    a.InvalidModelStateResponseFactory = context =>
    {
        var problemDetails = new CustomBadRequest(context);

        return new BadRequestObjectResult(problemDetails)
        {
            ContentTypes = { “application/problem+json”, “application/problem+xml” }
        };
    };
});

Testing the custom bad request

Now that the custom bad request has been configured, let’s launch the application.

The customized HTTP 400 error messages that we have configured in our custom bad request class are visible.