December 30, 2021
Global Exception Handing in .NET 6 Web API
In this article, we'll go through how to handle exceptions in a more complex way and cleaner as well. We managed exceptions handling, and use these patterns to write clean code and more readable.
Normally, to handle exceptions we can use the try-catch block in our code and use finally keyword to clean up resources afterward.
To demonstrate the benefits of this approach, we will first handle errors with a try-catch block and then rewrite our code with built-in middleware and our custom middleware for global exceptions handling.
We'll going to use an ASP.NET Core Web API project to explain these features so let create an Web API project.
Exception Handling with Try-Catch Block
When it comes to quick exception handling, the try-catch block is our go-to approach. Let's look at a code snippet that demonstrates the same thing.
1 2 3 4 5 6 7 8 9 10 11 12 13
[HttpGet] public IActionResult Get() { try { var persons = GetPersons(); //Assume you get persons data here which is also likely to throw an exception in certain cases. return Ok(persons); } catch (Exception ex) { return StatusCode(500); } }
As you can see that here's a basic implementation that we're all familiar with. Assume GetPersons() is a service call that is also thrown an exceptions due to external factors. In this scenario, the thrown exception is caught by the catch block, which is returning a status code of 500 Internal Server Error.
Exception Handling with Built-In Middleware
Built in UseExceptionHandler middleware is included with ASP.NET Core applications by default. When configured in the web application builder, this adds a middleware to the application's pipeline that will catch any exceptions in and out of the application
Let's take a look at how UseExceptionHandler is implemented.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
app.UseExceptionHandler(appError => { appError.Run(async context => { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.ContentType = "application/json"; var contextFeature = context.Features.Get<IExceptionHandlerFeature>(); if(contextFeature != null) { await context.Response.WriteAsync(new ErrorDetails() { StatusCode = context.Response.StatusCode, Message = "Internal Server Error." }.ToString()); } }); });
In the code above, we’ve registered the UseExceptionHandler middleware. Then, we’ve populated the status code and the content type of our response and finally returned the response with the custom created object.
Let’s remove the try-catch block from first section code
1 2 3 4 5 6 7
[HttpGet] public IActionResult Get() { var persons = GetPersons(); //Assume you get persons data here which is also likely to throw an exception in certain cases. throw new Exception("Exception while get persons data."); return Ok(persons); }
And there you go. Our action method is much cleaner now and what’s more important we can reuse this functionality to write more readable actions in the future.
So let’s inspect the response result
1 2 3 4
{ "StatusCode": 500, "Message" : "Internal Server Error." }
Exception Handling with Custom Middleware
Let’s create a class ExceptionHandlerMiddleware.cs, we are going to modify that class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
public class ExceptionHandlerMiddleware { private readonly RequestDelegate _next; public ExceptionHandlerMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { try { await _next(context); } catch (Exception error) { var response = context.Response; response.ContentType = "application/json"; var responseModel = await Result<string>.FailAsync(error.Message); switch (error) { case ApiException e: // custom application error response.StatusCode = (int)HttpStatusCode.BadRequest; break; case KeyNotFoundException e: // not found error response.StatusCode = (int)HttpStatusCode.NotFound; break; default: // unhandled error response.StatusCode = (int)HttpStatusCode.InternalServerError; break; } var result = JsonSerializer.Serialize(responseModel); await response.WriteAsync(result); } } }
The _next parameter of RequestDelegate type is a function delegate that can process our HTTP requests.
After the registration process, we need to create the InvokeAsync() method. RequestDelegate can’t process requests without it.
If everything goes well, the _next delegate should process the request and the Get action from our controller should generate a successful response. But if a request is unsuccessful (and it is, because we are forcing exception), our middleware will trigger the catch block and call the exception with response status code and content type and return a response.
Let’s also create some custom exception. Make sure that you inherit Exception as the base class. Here is how the custom exception looks like.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class ApiException : Exception { public ApiException() : base() { } public ApiException(string message) : base(message) { } public ApiException(string message, params object[] args) : base(string.Format(CultureInfo.CurrentCulture, message, args)) { } }
Finally, let’s use this method in the web application builder.
1
app.UseMiddleware<ExceptionHandlerMiddleware>();
So there you have it!
We had learned how to handle exceptions in a more sophisticated way and cleaner as well. Now, we have a completely custom-built error handling mechanism, all in one place.
That's it for now. Keep coding and enjoy exploring !!!
I’m glad you’re here. I like the simple so I created this minimalist site to share what I’ve learned interesting during my journey in technologies and in life. Keep coding and enjoy exploring !!!