Custom Exception Handling Middleware in .Net Core
Exception handling is a critical aspect of developing resilient and reliable applications. In .NET Core, the introduction of middleware has provided developers with a powerful tool to manage exceptions effectively. In this blog post, we’ll explore the importance of exception handling middleware in .NET Core and I’ll guide you through its implementation.
What is Exception Handling Middleware?
Middleware is software components that are assembled into the application pipeline to handle requests and responses.
Exception handling middleware in .NET Core plays a pivotal role in intercepting and managing exceptions throughout the application’s request-processing pipeline. This middleware allows developers to centralize error-handling logic, making it more modular and easier to maintain.
Exception handling middleware fits into this pipeline, allowing developers to intercept exceptions, log relevant information, and return appropriate responses to clients.
Why Handling Exceptions?
Exception handling is not just about catching errors; it’s about gracefully managing unexpected situations to prevent application crashes and enhance the user experience. Without proper exception handling, unhandled errors can lead to security vulnerabilities and degrade the overall performance of your application.
Implementing Global Custom Exception Handling Middleware
Let’s walk through the steps of implementing a global custom exception handling middleware in a .NET Core Web API.
Step 1: Create a new “ExceptionHandlingMiddleware” class in WebApi -> Middlewares folder as follows:
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
public ExceptionHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IWebHostEnvironment env)
{
try
{
await _next(context);
}
catch (Exception exception)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var Source = !string.IsNullOrEmpty(exception.Source) ? exception.Source : string.Empty;
if (env.IsDevelopment())
{
var InnerException = exception.InnerException != null ? exception.InnerException.Message : string.Empty;
var StackTrace = !string.IsNullOrEmpty(exception.StackTrace) ? exception.StackTrace.Replace("\r\n", Environment.NewLine).Trim() : string.Empty;
var htmlBody = MessageBuilder.BuildExceptionMessage(context, exception);
var response = ApiResponseBuilder.GenerateInternalServerError(null, $"{Source}-{exception.Message}", StackTrace);
await context.Response.WriteAsJsonAsync(response);
}
else
{
var response = ApiResponseBuilder.GenerateInternalServerError(null, $"{Source}-{exception.Message}", null);
await context.Response.WriteAsJsonAsync(response);
}
}
}
}
Note: The htmlBody variable in the above code contains the exception details in HTML format. It can be used to send as an email to the technical team so that they can work on the exception using these details. if you don’t want this functionality, you can comment out that line and proceed.
However, if you want, the HTML body of the email can be generated using string builder from a static “MessageBuilder” class in Application -> Helpers folder as given below:
public static class MessageBuilder
{
public static string BuildExceptionMessage(HttpContext context, Exception exception)
{
var msgBuilder = new StringBuilder();
msgBuilder.AppendLine($"<h1>Message: {exception.Message}</h1>");
msgBuilder.AppendLine(context.User.Identity.IsAuthenticated
? $"Source: {exception.Source} User: {context.User.Identity.Name}<hr />"
: $"Source: {exception.Source}<hr />");
msgBuilder.AppendLine($"Request Path: {context.Request.Path}<hr />");
msgBuilder.AppendLine($"QueryString: {context.Request.QueryString}<hr />");
if (exception.StackTrace != null)
msgBuilder.AppendLine($"Stack Trace: {exception.StackTrace.Replace(Environment.NewLine, "<br />")}<hr />");
if (exception.InnerException != null)
msgBuilder.AppendLine($"Inner Exception<hr />{exception.InnerException?.Message.Replace(Environment.NewLine, "<br />")}<hr />");
if (context.Request.HasFormContentType && context.Request.Form != null && context.Request.Form.Count > 0)
{
msgBuilder.AppendLine("<table border=\"1\"><tr><td colspan=\"2\">Form collection:</td></tr>");
foreach (var (key, value) in context.Request.Form)
msgBuilder.AppendLine($"<tr><td>{key}</td><td>{value}</td></tr>");
msgBuilder.AppendLine("</table>");
}
return msgBuilder.ToString();
}
}
Step 2: Use the custom “ExceptionHandlingMiddleware” in the pipeline just after the HttpsRedirection middleware as follows:
other middlewares... (code lines removed for brevity)
app.UseMiddleware<ExceptionHandlingMiddleware>();
other middlewares... (code lines removed for brevity)
Watch Exception Handling Middleware Implementation in Action
Best Practices for Exception Handling Middleware
1. Consistency in Error Responses
Establish best practices for providing consistent and standardized error responses to enhance the overall user experience:
- Use HTTP status codes appropriately.
- Include relevant error details in the response body.
- Ensure consistency in error response formats.
2. Security Considerations
Understand and implement security best practices to safeguard your application against potential vulnerabilities introduced by exception handling mechanisms:
- Avoid exposing sensitive information in error responses.
- Implement proper authorization checks in exception handling code.
Conclusion
Exception handling middleware in .NET Core is a powerful tool for creating robust and reliable applications. By understanding its importance, implementing best practices, and following a comprehensive guide for setup and customization, you can ensure that your application handles exceptions gracefully, providing a better experience for both users and developers.
Remember, effective exception handling is not just about fixing errors; it’s about creating a resilient and user-friendly application that can gracefully handle the unexpected.