Because (ASP).NET 8 is in preview it doesn't mean we can't start trying out some of the new features.
In this post, we'll take a look at the new
IExceptionHandler interface, which is introduced in Preview 5, to turn exceptions into Problem Details.
- Default API behavior
- Problem Details
- Problem Details with ASP.NET
- Exception Handler
Default API behavior link
Before we start introducing problem details and the exception handler, let's refresh our memory on how ASP.NET Core handles exceptions by default. An important detail to note is that the default behavior is different between development and production environments. Throughout this post, I'm only going to focus on the production environment.
When an endpoint is invoked and an exception is thrown while the request is being processed, the endpoint returns a 500 Internal Server Error response.
This is different when the exception is caught, and handled by the developer.
An example of this is that the logic within the endpoint is wrapped in a try-catch block, and the catch block returns a Bad Request (
This results in a 400 Bad Request response.
These responses are not very helpful for the consumer of the API.
Problem Details link
Problem Details is a described format for returning error information in HTTP API responses. While it's still in the "Proposed Standard" status, it's already widely used in the industry and built into ASP.NET Core.
Problem Details for HTTP APIs defines a "problem detail" as a way to carry machine- readable details of errors in an HTTP response to avoid the need to define new error response formats for HTTP APIs.
The Problem Details format looks like this:
Besides the described format, this object can also be extended with additional members to provide more information, these are called "Extension Members".
ASP.NET Core already has built-in support for Problem Details.
To enable it, you need to invoke the
IServiceCollection.UseProblemDetails() extension method and the
IApplicationBuilder.UseStatusCodePages() extension method.
Now, when we invoke an endpoint that returns a non-successful status code, we get a Problem Details response, including a response body.
To have the same behavior for exceptions, you also need to call the
IApplicationBuilder.UseExceptionHandler() extension method.
Resulting in the following response when the application throws an unhandled exception.
The result is already better than the default behavior, but we can improve this.
Exception Handler link
Before the addition of
IExceptionHandler exception handler, we could make use of a Exception Handler Page or a Exception Handler Lambda. Both of these options are still available, but the new
IExceptionHandler interface provides a more clean and flexible way of handling exceptions in my opinion.
An exception handler is a class that implements the
IExceptionHandler interface, and is registered by using
The reasons why I find this better than the other options are that it's more explicit.
It can also inject dependencies, short-circuit the pipeline or it can create a chain of multiple exception handlers, and the handler has access to the exception (previously you could get ahold of the exception with
To write your own exception handler, you need to implement the
IExceptionHandler interface, which looks as follows:
TryHandleAsync implementation receives the
HttpContext and the
Exception, and needs to return a
If the handler returns
true, the exception is considered handled, and the request pipeline is short-circuited.
When the value
false is returned, the default flow continues and a possible next exception handler will be invoked.
A simple implementation of an exception handler that returns a Problem Details response based on the thrown exception could look like this.
For all exceptions that are thrown within the application, this handler will be invoked.
Let's go over it to see what it does. The handler sets the HTTP response status code, and creates and writes a Problem Details response object directly to the response using the information from the exception.
A more advanced implementation would use the exception type to return a different Problem Details response (and status code) for specific exception types.
The last step before this handler can be used is to register the exception handler using the
IServiceCollection.AddExceptionHandler() extension method.
When we invoke an endpoint that throws an exception, we get a Problem Details response containing more information about the exception.
An important detail to note is that you should be careful with the information you return in the response. You don't want to leak sensitive information to the consumer of the API. This can be in the form of technical information, or business information.
In the previous example, we created a Problem Details response manually. While this works, there's a more suffocated solution available to return a Problem Details response.
Do you remember that I mentioned that ASP.NET has built-in support for Problem Details?
It doesn't only mean that problem details are returned for non-successful responses, nor does it mean that the
ProblemDetails class exists in the framework.
It also means that there's the
IProblemDetailsService service, which is available and can be used to create a Problem Details response object.
The service is provided when the
IServiceCollection.AddProblemDetails() method is invoked.
This means that we can refactor our exception handler to use the
IProblemDetailsService service to create the Problem Details response.
The cleaned-up version of the exception handler looks like this.
After this change, the response is still the same as before.
But, if you have a sharp eye, you might have noticed several small differences.
Because we use the
- the content type is now
- we're not required to set the
Statusproperty on the
ProblemDetailsinstance because the
IProblemDetailsServiceservice will set it for us using the status code of the response;
Instead of using the default Problem Details services you can also write your own implementation of IProblemDetailsWriter, but I haven't found a good use-case for this yet. If you have one, please let me know!
I also mentioned that problem details can be extended with additional properties.
One way of doing this is by providing these members during the instantiation of the
But, we can also configure the
IProblemDetailsService service to add default properties that we want to add to every Problem Details response.
For this, we can use the overload of the
In the example below, we add the trace identifier and the instance (the endpoint that is invoked) to the Problem Details response.
Resulting in the following response object when an exception is thrown.
In this post, I implemented the
ExceptionToProblemDetailsHandler class that implements the newly introduced
IExceptionHandler interface in ASP.NET Core 8.
The exception handler uses the Problem Details Service to translate thrown exceptions into Problem Details response objects.
The result is a standardized and better experience for your API consumers when the request is invalid, or when something went wrong during the processing of the request.
My Microsoft Learn Collection contains the resources that I used to learn these error handling features in ASP.NET. The resources also contain more information about the other error-handling features.
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.