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”; }
[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);
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.