Global Exception Handling in ASP.NET Core – Ultimate Guide
In this article, we will learn about Global Exception Handling in ASP.NET Core applications. Exceptions are something inevitable in any application however well the codebase is. This can usually occur due to external factors as well, like network issues and so on. If these exceptions are not handled well within the application, it may even lead the entire application to terminations and data loss.
The source code for the implementation can be found here.
Getting started with Exception Handling in ASP.NET Core
For this demonstration, We will be working on a new ASP.NET Core Web API Project. I will be using Visual Studio 2019 as my default IDE.
The try-catch block is our go-to approach when it comes to quick exception handling. Let’s see a code snippet that demonstrates the same.
[HttpGet] public IActionResult Get() { try { var data = GetData(); //Assume you get some data here which is also likely to throw an exception in certain cases. return Ok(data); } catch (Exception ex) { _logger.LogError(ex.Message); return StatusCode(500); } }
Here is a basic implementation that we are all used to, yeah? Assume, the method GetData() is a service call that is also prone to exceptions due to certain external factors. The thrown exception is caught by the catch block whose responsibility is to log the error to the console and returns a status code of 500 Internal Server Error in this scenario.
To learn more about logging in ASP.NET Core Applications, I recommend you to go through the following articles that demonstrate Logging with probably the best 2 Logging Frameworks for ASP.NET Core – Serilog & NLog
Let’s say that there was an exception during the execution of the Get() method. The below code is the exception that gets triggered.
throw new Exception("An error occurred...");
Here is what you would be seeing on Swagger.

The Console may get you a bit more details on the exception, like the line number and other trace logs.

Although this is a simple way for handling exceptions, this can also increase the lines of code of our application. Yes, you could have this approach for very simple and small applications. Imagine having to write the try-catch block in each and every controller’s action and other service methods. Pretty repetitive and not feasible, yeah?
It would be ideal if there was a way to handle all the exceptions centrally in one location, right? In the next sections, we will see 2 such approaches that can drastically improve our exception handling mechanism by isolating all the handling logics to a single area. This not only gives a better codebase but a more controlled application with even lesser exception handling concerns.
Default Exception Handling Middleware in ASP.NET Core
To make things easier, UseExceptionHandler Middleware comes out of the box with ASP.NET Core applications. This when configured in the Configure method of the startup class adds a middleware to the pipeline of the application that will catch any exceptions in and out of the application. That’s how Middlewares and pipelines work, yeah?
Let’s see how UseExceptionHandler is implemented. Open up the Configure method in the Startup class of your ASP.NET Core application and configure the following.
app.UseExceptionHandler( options => { options.Run(async context => { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; context.Response.ContentType = "text/html"; var exceptionObject = context.Features.Get<IExceptionHandlerFeature>(); if (null != exceptionObject) { var errorMessage = $"{exceptionObject.Error.Message}"; await context.Response.WriteAsync(errorMessage).ConfigureAwait(false); }}); } );
This is a very basic setup & usage of UseExceptionHandler Middleware. So, whenever there is an exception that is detected within the Pipeline of the application, the control falls back to this middleware, which in return will send a custom response to the request sender.
In this case, a status code of 400 Bad Request is sent along with the Message content of the original exception which in our scenario is ‘An error occurred…’. Pretty straight-forward, yeah? Here is how the exception is displayed on Swagger.

Now, whenever there is an exception thrown in any part of the application, this middleware catches it and throws the required exception back to the consumer. Much cleaned-up code, yeah? But there are still more ways to make this better, by miles.
Custom Middleware – Global Exception Handling In ASP.NET Core
In this section let’s create a Custom Global Exception Handling Middleware that gives even more control to the developer and makes the entire process much better.
Custom Global Exception Handling Middleware – Firstly, what is it? It’s a piece of code that can be configured as a middleware in the ASP.NET Core pipeline which contains our custom error handling logics. There are a variety of exceptions that can be caught by this pipeline.
We will also be creating Custom Exception classes that can essentially make your application throw more sensible exceptions that can be easily understood.
But before that, let’s build a Response class that I recommend to be a part of every project you build, at least the concept. So, the idea is to make your ASP.NET Core API send uniform responses no matter what kind of requests it gets hit with. This make the work easier for whoever is consuming your API. Additionally it gives a much experience while developing.
Create a new class ApiResponse and copy down the following.
public class ApiResponse<T> { public T Data { get; set; } public bool Succeeded { get; set; } public string Message { get; set; } public static ApiResponse<T> Fail(string errorMessage) { return new ApiResponse<T> { Succeeded = false, Message = errorMessage }; } public static ApiResponse<T> Success(T data) { return new ApiResponse<T> { Succeeded = true, Data = data }; } }
The ApiResponse class is of a generic type, meaning any kind of data can be passed along with it. Data property will hold the actual data returned from the server. Message contains any Exceptions or Info message in string type. And finally there is a boolean that denotes if the request is a success. You can add multiple other properties as well depending on your requirement.
We also have Fail and Success method that is built specifically for our Exception handling scenario. You can find how this is being used in the upcoming sections.
As mentioned earlier, let’s also create a custom exception. Create a new class and name it SomeException.cs or anything. Make sure that you inherit Exception as the base class. Here is how the custom exception looks like.
public class SomeException : Exception { public SomeException() : base() { } public SomeException(string message) : base(message) { } public SomeException(string message, params object[] args) : base(String.Format(CultureInfo.CurrentCulture, message, args)) { } }
Here is how you would be using this Custom Exception class that we created now.
throw new SomeException("An error occurred...");
Get the idea, right? In this way you can actually differentiate between exceptions. To get even more clarity related to this scenario, let’s say we have other custom exceptions like ProductNotFoundException , StockExpiredException, CustomerInvalidException and so on. Just give some meaningful names so that you can easily identify. Now you can use these exception classes wherever the specific exception arises. This sends the related exception to the middleware, which has logics to handle it.
Now, let’s create the Global Exception Handling Middleware. Create a new class and name it ErrorHandlerMiddleware.cs
public class ErrorHandlerMiddleware { private readonly RequestDelegate _next; public ErrorHandlerMiddleware(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 = ApiResponse<string>.Fail(error.Message); switch (error) { case SomeException 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); } } }
Line 3 – RequestDelegate denotes a HTTP Request completion.
Line 10 – A simple try-catch block over the request delegate. It means that whenever there is an exception of any type in the pipeline for the current request, control goes to the catch block. In this middleware, Catch block has all the goodness.
Line 14 – Catches all the Exceptions. Remember, all our custom exceptions are derived from the Exception base class.
Line 18 – Creates an APIReponse Model out of the error message using the Fail method that we created earlier.
Line 21 – In case the caught exception is of type SomeException, the status code is set to BadRequest. You get the idea, yeah? The other exceptions are also handled in a similar fashion.
Line 34 – Finally, the created api-response model is serialized and send as a response.
Before running this implementation, make sure that you don’t miss adding this middleware to the application pipeline. Open up the Startup.cs / Configure method and add in the following line.
app.UseMiddleware<ErrorHandlerMiddleware>();
Make sure that you comment out or delete the UseExceptionHandler default middleware as it may cause unwanted clashes. It doesn’t make sense to have multiple middlewares doing the same thing, yeah?
I also assume that you have done the necessary changes that will throw the SomeException Exception in the Get method of the default controller you are working with.
With that done, let’s run the application and see how the error get’s displayed on Swagger.

There you go! You can see how well built the response is and how easy it is to read what the API has to say to the client. Now, we have a completely custom-built error handling mechanism, all in one place. And yes, of course as mentioned earlier, you are always free to add more properties to the API Reponses class that suits your application’s needs.
I have been using this approach for literally all of my open source projects, and it’s With that, let’s wrap up the article for now 😉
Summary
In this article, we have looked through various ways to implement Exception handling in our ASP.NET Core applications. The favorite approach should definitely be the one where we implemented Global Exception Handling in ASP.NET Core using Custom Middlewares. You can also find the complete source code on my Github here. Have any suggestions or questions? Feel free to leave them in the comments section below. Thanks and Happy Coding! 😀
Thank you very much for this article.
Take care that some errors are not exceptions. See my question on stackoverflow: https://stackoverflow.com/questions/61269818/how-can-i-catch-asp-net-core-3-1-url-parsing-error-with-my-custom-exception-mi
Hi. What do you think about problemFactory?
https://docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-5.0#implement-problemdetailsfactory
Should I return BadRequest in Controller or throw an exception that will be handled in the middleware?
Thank you, Mukesh.
Nice article.
Hi, Mukesh!
Did I understand correctly that if we call an exception «throw new SomeException(“An error occurred…”);» it will fall on the current line and after we will see information about it in middleware? I just thought that if there is an error, we will not see an exception on the line where it occurred
Hi Mukesh!
How and where are u implementing ApiResponse.Success() method?
Very helpful, thank you! 😀
I ended up using the extension method `response.WriteAsJsonAsync(responseModel)` instead of the below:
“`
var result = JsonSerializer.Serialize(responseModel);
await response.WriteAsync(result);
“`