What is Global Exception Handling?
The Global Exception Handler is a type of workflow designed to determine the project’s behavior when encountering an execution error. Only one Global Exception Handler can be set per automation project. It is not available for library projects, only processes.
Global Exception Handling has 2 arguments:
ErrorInfo: Stores information about the error which is thrown. It also shows the error information if the workflow is failed. You can use ActivityInfo property to get the name of the activity which threw the exception and view it in the output.
Result: It is used to determine the behavior of the process when it encounters an error. You can use the following values to the result arguments:
1. Continue - The exception is re-thrown.
2. Ignore - The exception is ignored, and the execution continues from the next
activity.
3. Retry - The activity which threw the exception is retried. Use the RetryCount
method for errorInfo to count the number of times the activity is retried.
4. Abort - The execution stops after running the current Global Exception Handler.
The exception-handling features help us deal with unforeseen errors which could appear in our code. To handle exceptions we can use the try-catch block in our code as well as the final keyword to clean up resources afterward.
Implementing Global Error Handling in ASP.NET Core
We will be working on a new ASP.NET Core Web API Project for this demonstration. we 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.
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 of 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.
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 logic 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 in ASP.NET Core
UseExceptionHandler Middleware comes out of the box with ASP.NET Core applications to make things easier. 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.
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…’. 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.
Global Exception Handling In ASP.NET Core
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.
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 use this Custom Exception class we created now.
throw new SomeException("An error occurred...");
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 logic 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 Reqanest 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 blocthe k 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. The other exceptions are also handled in a similsimilarly– 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.
Resource: Codewithmukesh.com
The Tech Platform
Comments