.NET Global Exception Handling: 3 Techniques Beyond Try/Catch Blocks
1. Middleware
using System.Text.Json;namespace HARDCODE.API.Middlewares{public class ExceptionHandlingMiddleware{private readonly RequestDelegate _next;private readonly ILogger _logger;public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger){_next = next;_logger = logger;}public async Task Invoke(HttpContext context){try{await _next(context);}catch (Exception ex){// Log exception here_logger.LogInformation("An exception occurred");_logger.LogError(ex, ex.Message);// Perform necessary error handling logic hereawait HandleExceptionAsync(context, ex);}}private static Task HandleExceptionAsync(HttpContext context, Exception exception){var contextFeature = exception;int statusCode;object response;switch (contextFeature){case UnauthorizedAccessException:statusCode = StatusCodes.Status401Unauthorized;response = new{title = "Unauthorized",status = statusCode,message = contextFeature.Message};break;default:statusCode = StatusCodes.Status500InternalServerError;response = new{title = "Internal server error",status = statusCode,message = contextFeature.Message,};break;}context.Response.ContentType = "application/json";context.Response.StatusCode = statusCode;return context.Response.WriteAsync(JsonSerializer.Serialize(response));}}}
app.UseMiddleware<ExceptionHandlingMiddleware>();
As you can see it's just a simple line which will add your middleware and injects the needed dependencies and after that you're good to go and now you have a global exception handling service that made your response readable and elegant, and your error is now logged, and you can open your logs and find out the specific details of the exception that occurred.
But you know, using a middleware is a bit generic for exception handling. What about if I needed a more specific approach. One which seems more in the right place like instead of UseMiddleware we can apply our logic in UseExceptionHandler.
2. Extension
Now I know it's tempting to use UseExceptionHandler as it is but understand that to use this -out of the box- middleware we must first extend the IApplicationBuilder in a separate class to encapsulate the logic rather than just having it just lying around in program.cs.
So, let's create a class called ExceptionHandlingExtension which includes a member function ConfigureExceptionHandler that allows us to use the UseExceptionHandler middleware.
using Microsoft.AspNetCore.Diagnostics;
using System.Text.Json;
namespace HARDCODE.API.Extensions
{
public static class ExceptionHandlingExtension
{
public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILogger logger)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
// Log exception here
logger.LogInformation("An exception occurred");
logger.LogError(contextFeature.Error, contextFeature.Error.Message);
// Perform necessary error handling logic here
int statusCode;
object response;
switch (contextFeature.Error)
{
case UnauthorizedAccessException:
statusCode = StatusCodes.Status401Unauthorized;
response = new
{
title = "Unauthorized",
status = statusCode,
message = contextFeature.Error.Message
};
break;
default:
statusCode = StatusCodes.Status500InternalServerError;
response = new
{
title = "Internal server error",
status = statusCode,
message = contextFeature.Error.Message,
};
break;
}
context.Response.ContentType = "application/json";
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(JsonSerializer.Serialize(response));
}
});
});
}
}
}
As you can see it pretty much does the same thing as the first approach it's just different set-up. The logger should be injected manually as it will not be injected automatically like the middleware in the first approach.
This is how you register this service in program.cs and notice how we have to manually inject the logger.
app.ConfigureExceptionHandler(app.Logger);
3. Filters
using Microsoft.AspNetCore.Mvc.Filters;using Microsoft.AspNetCore.Mvc;namespace HARDCODE.API.Filters{public class GlobalExceptionFilter : IExceptionFilter{private readonly ILogger<GlobalExceptionFilter> _logger;public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger){_logger = logger;}public void OnException(ExceptionContext context){var exception = context.Exception;if(exception == null){return;}// Log exception here_logger.LogInformation("An exception occurred");_logger.LogError(exception, exception.Message);// Perform necessary error handling logic hereint status = StatusCodes.Status500InternalServerError;var message = context.Exception.Message;var title = "Internal server error";switch (context.Exception){case UnauthorizedAccessException:status = StatusCodes.Status401Unauthorized;title = "Unauthorized";message = context.Exception.Message;break;}// Customize the response (e.g., return a JSON error object)context.Result = new ObjectResult(new { title, message, status }){StatusCode = status,ContentTypes = { "application/json" }};context.ExceptionHandled = true;}}}
builder.Services.AddControllers(options =>{options.Filters.Add<GlobalExceptionFilter>();});
Comments
Post a Comment