Action Filters in ASP.NET Core are blessings, I am telling you how

In modern web applications, separating concerns and maintaining clean, maintainable code is requisite. ASP.NET Core provides a powerful mechanism known as Action Filters to handle cross-cutting concerns like validation, logging, authentication, measuring performance, error handling, and more—without cluttering the code in controllers.

What Are Action Filters?

Action Filters are attributes in ASP.NET Core that allow you to run code before or after an action method executes. In ASP.NET Core, there are five types of filters, and before listing those types, let's first see what a filter component is in ASP.NET Core. Filters are components that allow a developer to run a piece of code before and after specific stages during the request processing pipeline. Now the types, there are five types of filters in ASP.NET Core.

  1. Authorization Filters
  2. Resource Filters
  3. Action Filters
  4. Exception Filters
  5. Result Filters

In this post, my focus is on Action Filters. There are a few built-in action filters provided by ASP.NET Core.

[HttpGet]
[ValidateAntiForgeryToken]
[Produces("application/json")]

And we can also create custom action filters for any of the following purposes.

  • Logging: Track incoming requests and responses
  • Validation: Check model state or headers
  • Authorization Rules: Implement custom rules before action execution.
  • Error handling: Centralized exception catching.
  • Performance measurement: Measure the execution time.
  • Modifying request or response data: Manipulate headers, ViewBag, ViewData, HttpContext, or responses.
  • Caching: You can check and return cached results before executing an action or store results afterward.
  • Pre/Post processing: Inject shared logic before or after any action method runs.

Using Action Filters

I will create a separate blog post to discuss above mentioned purposes in detail with a real world examples from my experience but for this post let's try to create one to calculate the execution time of a request, for which I will create a class ExecutionTimeFilter implementing IActionFilter, which has two methods that I will need to implement, one is OnActionExecuting and other one is OnActionExecuted. The first method gets executed before going into the action, and the other one gets executed after the action.

public class ExecutionTimeFilter : IActionFilter
{
    private Stopwatch _stopwatch;

    public void OnActionExecuting(ActionExecutingContext context)
    {
        _stopwatch = Stopwatch.StartNew();
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        _stopwatch.Stop();
        var elapsed = _stopwatch.ElapsedMilliseconds;
        Console.WriteLine($"Action executed in {elapsed} ms");
    }
}
[ExecutionTimeFilter]
public IActionResult Index()
{
    return View();
}

Can we only use an Action Filter at the action level? Certainly not, we can use it at the action level, controller level, and even globally, based on the purpose of the Action Filter and the use case we want to cover. For the action level, it can be used like:

[ExecutionTimeFilter]
public IActionResult About() { ... }

For the controller level, it can be annotated on top of the controller, and for every action inside that controller, that Action Filter will be executed:

[ExecutionTimeFilter]
public class HomeController : Controller { ... }

If we want to execute on every request, doesn't matter which controller, then we can use it globally:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<ExecutionTimeFilter>();
});

Action filters, in fact, all the filters in ASP.NET Core have a certain purpose, so while using them we should keep the following points in mind:

  • Keep filters focused and single-responsibility
  • Avoid putting business logic inside filters
  • Use dependency injection for services

Conclusion

Action filters in ASP.NET Core provide a clean and reusable way to handle cross-cutting concerns across your app. Try implementing a custom filter today and enjoy cleaner controller code!