How to Create Custom Exception Filter in .Net Core C#?
.NET provides a hierarchy of built-in exception classes that are derived from the fundamental Exception base class. However, there may be times when none of the predefined exceptions meet your specific needs or requirements. In such case, you can create your own exception classes by deriving from the Exception class. In this article, we will discuss how to create custom exceptions in .Net Core using ExceptionFilterAttribute and implementing a global exception-handling mechanism in the Web API .Net Core application.
– An error might occur at any layer(API method, business, data access layers) in the application during the execution of a web API method and all those errors should be captured by a single exception filter class,
– capture & log requested API (URL path), error message & full stack trace,
– return a custom user-friendly message in response body,
– should not use old-fashioned try-catch blocks in any method.
What is ExceptionFilterAttribute?
In .NET Core, ExceptionFilterAttribute is a class that allows developers to create custom exception filters. Exception filters are used to handle exceptions that occur during the execution of ASP.NET Core MVC or Web API actions. These filters can be applied globally to all actions or selectively to specific controllers or actions. When an exception is thrown, an ExceptionFilterAttribute can catch it, perform custom logic (such as logging or generating a specific response), and potentially prevent the exception from propagating to the higher levels of the application. This helps in creating more robust and user-friendly error handling mechanisms in .NET Core applications. In other words, ExceptionFilterAttribute is an attribute used to define exception filters in ASP.NET Web API.
Create an Exception Filter class and override the OnException Method
The implementation for creating a custom Exception Filter is done in the following 3 simple steps.
Step 1: Create Custom Exception Class
To address unhandled exception scenarios, you can create a custom exception filter by defining a class that inherits the ExceptionFilterAttribute abstract class which implements the IExceptionFilter interface internally. In our example, we have given the class name as CustomExceptionAttribute.
Note: End the class name with Arrtibute.
Custom Exception Filter Class
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
namespace Product.Web
{
public class CustomExceptionAttribute : ExceptionFilterAttribute
{
private readonly ILogger _logger;
//constructor
public CustomExceptionAttribute(ILogger logger)
{
_logger = logger;
}
// override the OnException Method
public override void OnException(ExceptionContext ex)
{
var controllerActionDescriptor = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)ex.ActionDescriptor);
_logger.LogError("API: " + controllerActionDescriptor.AttributeRouteInfo.Template + "\r\n"
+ ex.Exception.Message + "\r\n"
+ ex.Exception.ToString());
//assigning custom response
ex.Result = JsonResultDto();
ex.HttpContext.Response.StatusCode = 400;
}
// override the OnException async Method
public override async Task OnExceptionAsync(ExceptionContext ex)
{
var controllerActionDescriptor = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)ex.ActionDescriptor);
_logger.LogError("API: " + controllerActionDescriptor.AttributeRouteInfo.Template + "\r\n"
+ ex.Exception.Message + "\r\n"
+ ex.Exception.ToString());
await Task.FromResult(0);
//assigning custom response
ex.Result = JsonResultDto();
ex.HttpContext.Response.StatusCode = 400;
}
// returning custom Json response
private JsonResult JsonResultDto()
{
return new JsonResult(
new
{
ErrorMessage = "Internal error occurred.",
HasError = true,
ResponseCode = 400
});
}
}
}
In the above code, we have overridden two OnException methods (one for normal and another for async methods). If any exception occurs while executing an API method (or in its internal sub-methods) then the code will hit the custom exception filter class. The OnException method will be invoked depending on whether the call is synchronous or asynchronous.
Here, we are using the ILogger interface to log the error details such as:
1) API Path,
2) Exception message,
3) full error stack trace.
The benefit of an Exception Filter is we can return a user-friendly error message to the front-end application in the response body. In the example, after logging the error, the JsonResultDto() method is called which will return the user-defined Json result to the user.
Step 2: Registering Exception Filter
We need to define the scope and register the custom exception filter globally, inside the ConfigureServices method in the startup.cs file.
Add services.AddScoped<CustomExceptionAttribute>(); line of code as shown below:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//...
//services.AddControllers();
//...
//register CustomExceptionAttribute class
services.AddScoped<CustomExceptionAttribute>();
}
}
Step 3: Apply Exception Filter on Controller or Action methods
To use a registered filter, it should be placed or decorated at the top of the Controller or Action as a ServiceType.
You can apply the Exception Filter at:
1) Controller Level, or
2) Action Level (one or more based on your requirement).
The example shows custom exception filter is applied at the Controller level, which means it is applicable to all API methods as well.
The GetProducts() method will throw System.FormatException error when code tries to convert a string variable (object) to an integer.
Add [ServiceFilter(typeof(CustomExceptionAttribute))] line of code above the Controller class, as shown below:
namespace Product.Web
{
[ServiceFilter(typeof(CustomExceptionAttribute))]
[ApiController]
[Route("[controller]")]
public class ProductController
{
[HttpGet]
[Route("GetProducts")]
public IActionResult GetProducts()
{
//example: System.FormatException: 'Input string was not in a correct format.'
object input = "test";
int result = Convert.ToInt32(input);
return Ok();
}
}
}
Testing ExceptionFilterAttribute – Screenshots
That’s it, now we can do a unit test and confirm if it is working as expected.
Swagger API Test
Call & execute API method from Swagger. It will return “400 – Bad Request” with a user-friendly JSON message in the response body, as shown in the below screenshot.

Debug code
The custom exception class will be called whenever an error occurs in the Controller’s Web API method and it will capture & log the following details:
1) on which API error occurred e.g., Path: “Product/GetProducts”
2) error message e.g., “Input string was not in a correct format.”
3) full stack trace
see the debugging screenshot below.
