Best Practices for Exception Handling in .NET Core
Today in this post, we will see a few best practices for Exception Handling in .NET Core-based applications.
In our last post, we learned about the Aspect-oriented programming concept and understood how better cross-cutting concerns can be addressed.
Handling exceptions is another aspect that can be handled and streamlined for application development.
Exception handling for some of us is all about using try-catch blocks and throw statements in the application. Isn’t it?
Most of the time proper analysis or design approach never gets identified for exception handling.
Today in this article, we will cover the below aspects,
Best practices for exception handling
It’s really important to differentiate between different types of exceptions.
This differentiation will help developers to design and manage these exceptions carefully while developing the code.
This consideration can be applied for customer-facing UI or API or Backend jobs etc.
Let’s understand these exceptions and their types and why they are important.
Business Exceptions
- Business exceptions are always different from technical exceptions/messages.
- In business exceptions – Customers should experience consistency in responses to operations.
- Business exceptions should not expose any technicalities of errors.
- The higher end of your application which is API or UI (customer-facing) needs to provide graceful error messages.
- Business exceptions should be as generic as possible and should talk about business operations. For example “Something went wrong, Please try again later “.
Technical Exceptions
Business exceptions are always mapped to Technical exceptions.
Whenever your application users see Business exceptions, your app will log more detailed Technical exceptions at the same time (mostly in the form of logging to some datastore – a file logging or SQL database loging or event viewer).
- Technical exceptions are mapped with business exceptions and eventually should provide all technical details of exceptions.
- Exception handling is an important aspect and an important cross-cutting concern.
- Try to streamline and manage exception handling code to commonplace/modules to provide consistency.
- Every exception needs to be handled properly.
- Exception handling should not hide exception details.
- Details of any Technical exceptions should include metadata like the origin of the error, file name, class name, method name, and code line number with the actual exception message.
- Technical exceptions should be logged with the stack trace details as possible. Stack trace keeps track of the origin of issues and other details.
- Do not swallow any exceptions without proper actions like logging etc.
- Always throw exceptions and make the highest layer of your application swallow it. The top layer of the application could be UI or API.
- Try to avoid re-setting stack trace details. Use Throw Vs Throw ex Vs Throw new statements aptly. Example,
- throw – Keep stack trace details
- throw ex- Reset the stack trace details
- Do not use empty catch blocks.
- Use your custom exception details as much as possible else if using a built-in exception type be vary on business vs technical details logging.
- For RESTFul API development apply HTTP Status based on the exception of the target resource.
Different Techniques of Handling Exception
- Global Exception Handler – Explained in the current article.
Using Global Exception Handler in .NET Core
.NET Core provides an interface called “IExceptionHandlerFeature” which provides the ability to handle exception details within an API pipeline.
As a first step please create a static class ‘ExceptionHandler’ as shown below.
public static class ExceptionHandler
{
public static void UseApiExceptionHandler(this IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
//if any exception then report it and log it
if (contextFeature != null)
{
//Technical Exception for troubleshooting
var logger = loggerFactory.CreateLogger("GlobalException");
logger.LogError($"Something went wrong: {contextFeature.Error}");
//Business exception - exit gracefully
await context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = "Something went wrongs.Please try again later"
}.ToString());
}
});
});
}
}
That’s all and you are all set to use this API in the pipeline by adding below the line of code in Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory logger)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
app.UseApiExceptionHandler(logger); //use global exception handlers
}
app.UseHttpsRedirection();
app.UseMvc();
}
With this approach, it’s important that other layers of application like BAL or Domain (DDD) or DAL should keep throwing an exception as a good practice.
These exceptions should be swallowed by the extreme layer of an application and depending on the application design, this top layer could be UI or API layer.
It’s all about a few best practices which developers can follow to take advantage of global exception handlers for effective exception handling.
Using the Global Middleware exception component
This is one of the most effective and preferred methods of handling exceptions globally.
I have talked about this technique in detail, please visit the below post for more details.
Using Exception filters in .NET Core
Using exception filters is also a good technique for handling exceptions.
The only limitation of this approach is that it’s effective for only API pipelines.
If there is an exception in other layers of your app apart from the API layer then this approach won’t be helpful.
Example – It won’t catch the exception that occurred in the middleware used in the app through the pipeline.
I have talked about this technique in detail, please visit the below post for more details.
Let’s look at the example of exception filters using .NET Core,
- Once you have a sample API available, add the sample class ‘ExceptionInterceptor’ as below. Below shows examples of handling Synchronous and Asynchronous actions. Please choose the best approach suitable for your application.
- The next step is how to handle this exception filter. Here we have a choice on the same. Just like action filters, we can control exception filters globally or controller or action level.
- Sync: Here you need to derive your filter class from the IExceptionFilter interface and implement 2 methods OnException.
- Async: Here you need to derive your filter class from the IExceptionFilter interface and implement 1 method OnExceptionAsync.
public class ServiceExceptionInterceptor : IAsyncExceptionFilter
{
public Task OnExceptionAsync(ExceptionContext context)
{
//Business exception-More generics for external world
var error = new ErrorDetails()
{
StatusCode = 500,
Message = "Something went wrong! Internal Server Error."
};
//Logs your technical exception with stack trace below
//
context.Result = new JsonResult(error);
return Task.CompletedTask;
}
}
Let’s enable this exception filter globally in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options=>options.Filters.Add(new ServiceExceptionInterceptor())).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
For ASP.NET Core 3.1 (IRouter routing and MVC template)
services.AddMvc(options=> options.Filters.Add(new ServiceExceptionInterceptor())).SetCompatibilityVersion(CompatibilityVersion.Version_3_1);
For ASP.NET Core 3.1 (endpoint routing and new template)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options => options.Filters.Add(new ServiceExceptionInterceptor()));
..
..
}
Generic exception code and message
Now let’s test the controller with some exceptions. Error response for most of the exceptions will be generic as below,
For both above approaches, I have used a simple class for returning a response as below,
Using Try-Catch-Throw – Best Practices
Understand the difference between throw vs throw ex vs throw new statements. Please visit the below post for more details.
Do you have any comments or ideas or any better suggestions to share?
Please sound off your comments below.
Happy Coding !!
Summary
In this article, we understood the differences between Business exceptions and Technical exceptions and also understood how bad exception handling can be misleading when trying to troubleshoot or find bugs. Good Exception handling should help a business user and the same time should save developers time finding technical issues.
Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published(2024) best practices and guidelines for software design and development.
For NET 6 Minimal APIs best practices?
Hi kiquenet -Above article, very much talk about basic guidelines for managing the exceptions. Do you have any specific questions?
Hi all,
In this article, you mention that business articles should be generic, but in great UI writing it is always mentioned that you must return meaningful errors.
Like ” You can not place 2 order at a moment.” which is directly help final user to be notified.
Regards
Hey Mohammad, Thanks for your query. Yes. The business error should be generic enough and should not expose any technicality( user id, or exception internal logs, server details, class name, or methods name) of errors. An error like “You can not place 2 orders at a moment.” can be considered as business error.You can very much define such multiple business exceptions based on business rules.
Thank you for your answer and updating. The problem is solved.
I wish you good work 🙂
Thank you! Glad to help you.
Thanks for article! I use it with creating class and an errors occured in Startup.cs like below:
services.AddMvc(options = > options.Filters.Add(new ServiceExceptionInterceptor())).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
it gives error options, gt and SetCompatibilityVersion as not exist. Can you help me? :))
Hello CA,
I fixed the syntax issue (caused by syntax highlighter plugin). You should be good now. Please check. Sorry for the inconvenience.Thank you for reaching out.
Hi Thanks. Which technique is better among all?
Hey Geor- Thanks for your query. It will more depend on the context which you are dealing with. Global middleware approach is most preferred. Overall middleware approach is suitable as it will be able to handle exception from other modules or middlewares in the application and also work with ASP.NET Core app/REST services(Webapi).
can we use same technique for MVC asp.net core UI app ?
Off course. It’s all simply you set proper route pages for error handling details. ASP.NET will be able to handle those routes. Thanks for your query.
Nice Article! Appreciate.
Can you please explain how logger object you are using for logging in exception filter options
Thanks, Pearson for your query.
For logger object you can simply use Loggerfactory to create logger instance and log details . The log will be logged based on log level configured in you settings or environment variable if any.