Last Updated on October 10, 2022 by Aram
In this tutorial I will show you how you can implement exception handling and logging in ASP.NET Core Web API.
You will learn the proper way of handling exceptions and writing these exceptions and any additional data into log files.
This is rather important and essential when your product is on live, since then you will have loads of users.
You need to know what is going wrong with your endpoints and backend. So you can detect and handle the exceptions in the correct way: either to return a graceful and meaningful response or otherwise find that bug and solve it once and for all.
Knowing of how to achieve this in ASP.NET Core is very important and interesting topic that you need to learn to help you when troubleshooting your application on production
Prerequisites
In this tutorial, we will be using the latest version of Visual Studio 2022 which will implicitly have the latest version of .NET 6
Also we will be testing our endpoints through Postman. Make sure to get latest version of it here.
After the fact that you have everything you need for the tutorial, let’s get started.
We will implement the exception handling and logging on one of our previous tutorials. So make sure to get latest or clone this repository from my GitHub account. I have recently updated the project in this repository to .NET 6.
Exception Handling in ASP.NET Core Web API
Some might tell you that the easy way to handle exception in your ASP.NET Core Web API is that you can always surround your controller function code with try catch blocks, that does work and nothing technically wrong with that, however surrounding each and every block of controller functions with try catch blocks would eventually render your code convoluted and hard to read.
Instead, you should always opt for the more organized, loosely coupled, generalized solutions which is having a middleware to handle your exceptions and gracefully return the proper response message structure for your APIs’ client.
Let’s start with implementing our custom exception handling middleware
Creating Exception Handling Middleware
In the project, create a new folder name it Middleware, and then inside it add a new class with name “ExceptionHandlingMiddleware”
Inside it we need to add this code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Net;
using System.Threading.Tasks;
namespace MoviesApi.Middleware
{
public class ExceptionHandlingMiddleware
{
public RequestDelegate requestDelegate;
public ExceptionHandlingMiddleware(RequestDelegate requestDelegate)
{
this.requestDelegate = requestDelegate;
}
public async Task Invoke(HttpContext context)
{
try
{
await requestDelegate(context);
}
catch (Exception ex)
{
await HandleException(context, ex);
}
}
private static Task HandleException(HttpContext context, Exception ex)
{
var errorMessage = JsonConvert.SerializeObject(new { Message = ex.Message, Code = "GE" });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return context.Response.WriteAsync(errorMessage);
}
}
}
|
Take a look at the errorMessage line of code, we introduced a new object having the message and a code;
You can show the message to your users, but in many times you must handle the localization at the API side.
Instead, you can return a code for your front-end where it will match it with its localization file based on the code instead of showing the message as-is.
In this case, I have chosen to use a code like “GE” short for General Exception, to represent that API was not successful to due a General Exception, so then the front-end or the client should match this with their localization local file and show the appropriate user-friendly localized message.
Also if you notice we can also control the http code that will be returned for the client, in some cases we might not return an Internal Server Error (500).
This is where we can detect the type of the exception and based on it we return the appropriate message, code and the http status code, such as 400 bad request.
I will show you how you can implement such use case a little bit later in this tutorial.
Now let’s proceed with integrating our middleware into the application pipeline to have it ready to intercept the exceptions.
Open your program.cs file and after you call the builder.Build() method you can inject the new Middleware into your API’s pipeline through the app instance:
1 |
app.UseMiddleware(typeof(ExceptionHandlingMiddleware));
|
Now, let’s trigger an exception in our MoviesController GetMovies method just to see what will happen to the API and how the exception will be handled and the response message returned:
1
2
3
4
5
6
|
[HttpGet]
public async Task<ActionResult<IEnumerable<Movie>>> GetMovies()
{
throw new System.Exception("An error occurred");
return await _context.Movies.ToListAsync();
}
|
Testing the API
Run the API, and switch to postman, and see what will happen once we call the GET /movies endpoint
See? We are getting the error message and the code in the response with an http 500 status.
What happened here is that our Exception Handling middleware intercepted the System exception, caught and processed it within its Invoke method;
The method that we overridden in our custom middleware implementation and added our try catch block with the needed HttpResponse message.
Adding Business Exception Class
As you have learnt in the previous section, we were able to properly handle a generic exception happening to one of our controller methods in the custom middleware.
Let’s see how we can trigger a custom business exception and handle it in our middleware and return a different response message and code for the front-end or the client.
In the Models folder, right click and choose add a class, let’s name it InvalidMovieException.
Inside it let’s add the below code:
1
2
3
4
5
6
7
8
9
10
11
|
using System;
namespace MoviesApi.Models
{
public class InvalidMovieException : Exception
{
public InvalidMovieException(string message) : base(message)
{
}
}
}
|
Simply, It is an empty class inheriting from the Exception class. We will use this class to verify the input of the get movie by id method.
Also we want to create a new class to represent the Error Model. We need to be able to update the error in case it is of a custom business exception. So again in the Models folder create a new class with name “Error” and let it have the below properties:
1
2
3
4
5
6
7
8
|
namespace MoviesApi.Models
{
public class Error
{
public string Message { get; set; }
public string Code { get; set; }
}
}
|
Now, open the MoviesController and inside the get Movie by id method, we will add a block to validate the input of the endpoint to have a valid id ( greater than 0) , see the below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
[HttpGet("{id}")]
public async Task<ActionResult<Movie>> GetMovie(int id)
{
if (id <= 0)
{
throw new InvalidMovieException("Invalid Movie Id");
}
var movie = await _context.Movies.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return movie;
}
|
Now let’s run the API again, and switch to postman, and call the endpoint /GET Get Movie By Id with the value 0
In this case, our exception handling middleware also worked correctly and handled the exception, but we don’t want to return a 500 http status code, and the response code should not be GE.
So what will do now is that we will add the required code to handle and properly report this business exception with the correct response.
Open the ExceptionHandlingMiddleware, and navigate to the HandleException method, see now the code how it should look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private static Task HandleException(HttpContext context, Exception ex)
{
var errorMessageObject = new Error { Message = ex.Message, Code = "GE" };
var statusCode = (int)HttpStatusCode.InternalServerError;
switch (ex)
{
case InvalidMovieException:
errorMessageObject.Code = "M001";
statusCode = (int)HttpStatusCode.BadRequest;
break;
}
var errorMessage = JsonConvert.SerializeObject(errorMessageObject);
context.Response.ContentType = "application/json";
context.Response.StatusCode = statusCode;
return context.Response.WriteAsync(errorMessage);
}
|
Let’s run the API again and see how the response will look like:
Great, that is much better and logical now, the http status code is 400 Bad Request because it is not an exception or error, it is just a wrong user input or invalid request, and the response Code is also different (M001) which makes sense.
You can feel free to build your own response codes list but always make sure to properly document them for your front-end developers or clients so that they can understand these codes and build the matching localization file at their side.
In the next section, we will learn how can we persist these exceptions and log them into local files in ASP.NET Core Web API.
Logging in ASP.NET Core Web API using Log4Net
You can pretty easily implement logging in ASP.NET Core Web API.
Actually, there are several ways to achieve the same result which is logging whatever information you want into files.
For more advanced and professional cases, you can use different file structure, such as elasticsearch, to write your files in a format that can be understood by comprehensive GUI tools like Kibana for reading and visualizing these logs, or even you can post these logs over a cloud-based services such as Amazon Cloudwatch.
One of the most beneficial use cases of logging is to write your exceptions, errors and any associated information to logs.
It would be super helpful for you to trace bugs and problems in your application or APIs on production environments, when your users start consuming your APIs and start using your product.
In this section of the tutorial, we will learn how to implement logging in ASP.NET Core Web API using Log4Net.
Log4Net
Log4Net is a very popular open source .NET Framework library for logging .NET applications. It has been around for quite a long time. Log4Net is a direct port from Log4j (Java Library by Apache Logging Services Project). Log4Net easily allows you to write whatever data you want into a variant of resources such as files.
So let’s bring in the nuget package to handle our logging in ASP.NET Core Web API using Log4Net.
Right click on your project name from the solution explorer and select “Manage Nuget Packages” , then search for Log4Net and install this library Microsoft.Extensions.Logging.Log4Net.AspNetCore
This will install both the logging extensions of Log4Net in ASP.NET Core as well as the Log4Net library itself.
Once this is installed, open Program.cs file and make sure you have the below code added after calling the WebApplication.CreateBuilder(args) method:
1
2
3
|
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddLog4Net("log4net.config");
|
Or, you can configure it using the below line:
1 |
builder.Host.ConfigureLogging(logging => logging.AddLog4Net("log4net.config"));
|
Notice how easy it is to integrate ASP.Net Core Web API in .NET 6 with Log4Net
Now we just need to add the log4net.config file so that the Log4Net would know what, how and where to write its logs.
Add a new file in the project with name “log4net.config”, it would include the xml configurations for the Log4Net library:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<log4net>
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
<appendToFile value="true" />
<file value="logfile.txt" />
<rollingStyle value="Date" />
<maximumfilesize value="100KB" />
<datePattern value="yyyyMMdd-HHmm" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger.%method [%line] - MESSAGE: %message%newline" />
</layout>
</appender>
<root>
<appender-ref ref="RollingFile" />
<level value="ALL" />
</root>
</log4net>
|
Log4Net Rolling Files
Run the API again. Navigate to the bin folder of the project in file explorer, you will notice a new file with name logfile.txt
And inside it you will see some logs related to the application start
Now that we have the logging ready in our ASP.NET Core Web API project, it will be very easy to use the new logging library to log our exception in our Middleware, we just need to accept the abstract ILogger of the logging extensions library, so the Log4Net logging class will be injected into the Middleware.
So, let’s open the ExceptionHandlingMiddleware and inside it let’s add a new argument in the Invoke method:
1
2
3
4
5
6
7
8
9
10
11
|
public async Task Invoke(HttpContext context, ILogger<ExceptionHandlingMiddleware> logger)
{
try
{
await requestDelegate(context);
}
catch (Exception ex)
{
await HandleException(context, ex, logger);
}
}
|
and in the HandleException, we will also have to accept the new argument:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private static Task HandleException(HttpContext context, Exception ex, ILogger<ExceptionHandlingMiddleware> logger)
{
logger.LogError(ex.ToString());
var errorMessageObject = new Error { Message = ex.Message, Code = "GE" };
var statusCode = (int)HttpStatusCode.InternalServerError;
switch (ex)
{
case InvalidMovieException:
errorMessageObject.Code = "M001";
statusCode = (int)HttpStatusCode.BadRequest;
break;
}
var errorMessage = JsonConvert.SerializeObject(errorMessageObject);
context.Response.ContentType = "application/json";
context.Response.StatusCode = statusCode;
return context.Response.WriteAsync(errorMessage);
}
|
we are using the logger object to log the error by passing the exception object’s ToString so that the exception message and its stack trace would be logged into our new log file.
Seeing the exception message and stack trace
Start the API once more and trigger a bad request from postman or swagger on browser, to see how the error message and stack trace would be logged into our log file:
Congratulations! Now you have learned how to handle exceptions and log them using Log4Net in ASP.NET Core Web API.
Logging in ASP.NET Core Web API using Serilog
As mentioned before there are several ways to start writing logs into your ASP.NET Core Web API project, above you’ve learned how to write logs using Log4Net.
I have another detailed tutorial that shows you how you can implement Logging with Serilog in ASP.NET Core Web API and it is updated to the .NET 6 version.
Check it out and let me know in the comments which logging mechanism or library you prefer to use.
Summary
It is essential to know how to deal with your exceptions particularly when your API is on production and you want to guarantee a swift integration with your front-end or your clients.
Another important topic is logging your exceptions to know when and what happened and how often is happening, that will give you the ability to trace your exceptions and fix them.
In the tutorial you learned both exception handling and logging using Log4Net in ASP.NET Web API. If you aren’t already doing so, you can go ahead and enhance your current RESTful APIs by implementing these very crucial features.
Try these out and let me know how it goes in the comments. If you think this tutorial added some value or knowledge to you, feel free to like and share it with others.
This tutorial uses the code-base of another important and interesting article that you can check it here:
Also you can take a look at my other articles:
- File Upload with Data using ASP.NET Core Web API
- Secure Angular Site using JWT Authentication with ASP.NET Core Web API
- Localization in ASP.NET Core Web API
- Google reCAPTCHA v3 Server Verification in ASP.NET Core Web API
- Apply JWT Access Tokens and Refresh Tokens in ASP.NET Core Web API 6
- Build RESTful APIs Using ASP.NET Core and Entity Framework Core
- Secure ASP.NET Core Web API using API Key Authentication
Bonus
Enjoy the tranquilizing combined tunes of the violin and piano. This is one of my favourite masterpieces from the Romantic era by Franz Schubert – “Ständchen” D957 (Serenade)