I am using ASP.NET Core for my new REST API project after using regular ASP.NET Web API for many years. I don't see any good way to handle exceptions in ASP.NET Core Web API. I tried to implement an exception handling filter/attribute:
public class ErrorHandlingFilter : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { HandleExceptionAsync(context); context.ExceptionHandled = true; } private static void HandleExceptionAsync(ExceptionContext context) { var exception = context.Exception; if (exception is MyNotFoundException) SetExceptionResult(context, exception, HttpStatusCode.NotFound); else if (exception is MyUnauthorizedException) SetExceptionResult(context, exception, HttpStatusCode.Unauthorized); else if (exception is MyException) SetExceptionResult(context, exception, HttpStatusCode.BadRequest); else SetExceptionResult(context, exception, HttpStatusCode.InternalServerError); } private static void SetExceptionResult( ExceptionContext context, Exception exception, HttpStatusCode code) { context.Result = new JsonResult(new ApiResponse(exception)) { StatusCode = (int)code }; } }
And here is my Startup filter registration:
services.AddMvc(options => { options.Filters.Add(new AuthorizationFilter()); options.Filters.Add(new ErrorHandlingFilter()); });
The issue I was having is that when an exception occurs in my AuthorizationFilter
it's not being handled by ErrorHandlingFilter
. I was expecting it to be caught there just like it worked with the old ASP.NET Web API.
So how can I catch all application exceptions as well as any exceptions from Action Filters?
Use the UseExceptionHandler middleware in ASP.NET Core So, to implement the global exception handler, we can use the benefits of the ASP.NET Core build-in Middleware. A middleware is indicated as a software component inserted into the request processing pipeline which handles the requests and responses.
Global Exception Filters With exception filters, you can customize how your Web API handles several exceptions by writing the exception filter class. Exception filters catch the unhandled exceptions in Web API. When an action method throws an unhandled exception, execution of the filter occurs.
Approach 1: UseExceptionHandler. Switch to the production mode for the app, startup file Configure method tells us: ASP.NET Core handles exception by calling UseExceptionHandler: public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env. IsDevelopment()) { app.
Simply add this middleware before ASP.NET routing into your middleware registrations.
app.UseExceptionHandler(c => c.Run(async context => { var exception = context.Features .Get<IExceptionHandlerPathFeature>() .Error; var response = new { error = exception.Message }; await context.Response.WriteAsJsonAsync(response); })); app.UseMvc(); // or .UseRouting() or .UseEndpoints()
Step 1. In your startup, register your exception handling route:
// It should be one of your very first registrations app.UseExceptionHandler("/error"); // Add this app.UseEndpoints(endpoints => endpoints.MapControllers());
Step 2. Create controller that will handle all exceptions and produce error response:
[AllowAnonymous] [ApiExplorerSettings(IgnoreApi = true)] public class ErrorsController : ControllerBase { [Route("error")] public MyErrorResponse Error() { var context = HttpContext.Features.Get<IExceptionHandlerFeature>(); var exception = context.Error; // Your exception var code = 500; // Internal Server Error by default if (exception is MyNotFoundException) code = 404; // Not Found else if (exception is MyUnauthException) code = 401; // Unauthorized else if (exception is MyException) code = 400; // Bad Request Response.StatusCode = code; // You can use HttpStatusCode enum instead return new MyErrorResponse(exception); // Your error model } }
A few important notes and observations:
[ApiExplorerSettings(IgnoreApi = true)]
is needed. Otherwise, it may break your Swashbuckle swaggerapp.UseExceptionHandler("/error");
has to be one of the very top registrations in your Startup Configure(...)
method. It's probably safe to place it at the top of the method.app.UseExceptionHandler("/error")
and in controller [Route("error")]
should be the same, to allow the controller handle exceptions redirected from exception handler middleware.Here is the link to official Microsoft documentation.
Implement your own response model and exceptions. This example is just a good starting point. Every service would need to handle exceptions in its own way. With the described approach you have full flexibility and control over handling exceptions and returning the right response from your service.
An example of error response model (just to give you some ideas):
public class MyErrorResponse { public string Type { get; set; } public string Message { get; set; } public string StackTrace { get; set; } public MyErrorResponse(Exception ex) { Type = ex.GetType().Name; Message = ex.Message; StackTrace = ex.ToString(); } }
For simpler services, you might want to implement http status code exception that would look like this:
public class HttpStatusException : Exception { public HttpStatusCode Status { get; private set; } public HttpStatusException(HttpStatusCode status, string msg) : base(msg) { Status = status; } }
This can be thrown from anywhere this way:
throw new HttpStatusCodeException(HttpStatusCode.NotFound, "User not found");
Then your handling code could be simplified to just this:
if (exception is HttpStatusException httpException) { code = (int) httpException.Status; }
HttpContext.Features.Get<IExceptionHandlerFeature>()
WAT?
ASP.NET Core developers embraced the concept of middlewares where different aspects of functionality such as Auth, MVC, Swagger etc. are separated and executed sequentially in the request processing pipeline. Each middleware has access to request context and can write into the response if needed. Taking exception handling out of MVC makes sense if it's important to handle errors from non-MVC middlewares the same way as MVC exceptions, which I find is very common in real world apps. So because built-in exception handling middleware is not a part of MVC, MVC itself knows nothing about it and vice versa, exception handling middleware doesn't really know where the exception is coming from, besides of course it knows that it happened somewhere down the pipe of request execution. But both may needed to be "connected" with one another. So when exception is not caught anywhere, exception handling middleware catches it and re-runs the pipeline for a route, registered in it. This is how you can "pass" exception handling back to MVC with consistent content negotiation or some other middleware if you wish. The exception itself is extracted from the common middleware context. Looks funny but gets the job done :).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With