Exception Handling Middleware
After many experiments with different exception handling approaches I ended up using middleware. It worked out the best for my ASP.NET Core Web API application. It handles application exceptions as well as exceptions from action filters and I have full control over exception handling and HTTP response. Here is my exception handling middleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
if (ex is MyNotFoundException) code = HttpStatusCode.NotFound;
else if (ex is MyUnauthorizedException) code = HttpStatusCode.Unauthorized;
else if (ex is MyException) code = HttpStatusCode.BadRequest;
var result = JsonConvert.SerializeObject(new { error = ex.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
Register it before MVC in Startup
class:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();
You can add stack trace, exception type name, error codes or anything you want to it. Very flexible. Here is an example of exception response:
{ "error": "Authentication token is not valid." }
Consider injecting IOptions<MvcJsonOptions>
to the Invoke
method to then use it when you serialize the response object to utilize ASP.NET MVC's serialization settings in JsonConvert.SerializeObject(errorObj, opts.Value.SerializerSettings)
for better serialization consistency across all endpoints.
Approach 2
There is another non-obvious API called UseExceptionHandler
that works "ok" for simple scenarious:
app.UseExceptionHandler(a => a.Run(async context =>
{
var feature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = feature.Error;
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(result);
}));
This is not a very obvious but easy way to set up exception handling. However I still prefer the middleware approach over it as I get more control with ability to inject necessary dependencies.