✨ Mastering Action Filters in ASP.NET Core - Clean Code & Cross-Cutting Concerns
A Practical Guide to Action Filters for Cleaner and More Maintainable ASP.NET Core APIs
If you’ve ever written the same piece of logic in multiple controllers or actions, chances are, Action Filters are what you’ve been looking for.
They’re one of the most powerful and underused features in ASP.NET Core — designed to help you keep your code clean, maintainable, and DRY (Don't Repeat Yourself).
Let’s dive into what they are, how they work, and when to use them.
🎯 What Are Action Filters?
In ASP.NET Core, Action Filters are a type of filter that allow you to hook into the execution pipeline of a controller action. They provide a way to execute custom code both before an action method runs and after it finishes.
At a high level, think of action filters as decorators around your controller actions. If you've ever found yourself repeating logging, validation, or timing logic across multiple controller methods, that's a strong sign that the logic belongs in a filter.
🧠 How They Fit Into the Request Lifecycle
When an HTTP request hits your ASP.NET Core application, it goes through a pipeline of middleware and then lands at a controller action. Filters sit in between the routing logic and the action method itself.
Action filters are one of several types of filters in ASP.NET Core:
Authorization filters – run first to check if the user is allowed
Resource filters – manage caching or resource access
Action filters – run before and after an action method executes
Exception filters – handle unhandled exceptions from actions
Result filters – run before and after the action result (e.g., a View or JSON response) executes
Among these, action filters are the most commonly used for general cross-cutting concerns.
🚦 The Two Key Methods
Action filters typically implement either IActionFilter
or IAsyncActionFilter
(for async logic). These interfaces define two methods:
OnActionExecuting(ActionExecutingContext context)
🔹 Runs before the action method starts
🔹 Great for input validation, logging, setting up context, etc.OnActionExecuted(ActionExecutedContext context)
🔹 Runs after the action method finishes
🔹 Perfect for logging results, cleaning up resources, handling post-processing
You can use either or both methods depending on your needs.
🔄 Synchronous vs Asynchronous Filters
Use
IActionFilter
orActionFilterAttribute
for simple, non-async logic.Use
IAsyncActionFilter
if your filter involves async code like database access, calling external APIs, etc.
For example, if you're logging response times with Stopwatch
, you can use async filters and await next()
to wrap the actual execution.
When to use filters:
Validate user input
Log requests/responses
Handle exceptions
Add headers
Enforce authorization rules
Measure performance
Basically, any cross-cutting concern that’s cluttering your controllers? Move it into a filter.
🧱 How to Create a Custom Action Filter
There are two common ways:
1. Implement IActionFilter
public class LogActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("Before action executes.");
}
public void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("After action executes.");
}
}
2. Use ActionFilterAttribute
public class LogAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("LogAttribute - Before");
}
public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("LogAttribute - After");
}
}
🧪 How to Apply It
You can apply filters at:
✅ Action level
[Log]
public IActionResult Index()
✅ Controller level
[Log]
public class HomeController : Controller
✅ Global level (in Startup.cs
)
services.AddControllers(options =>
{
options.Filters.Add<LogActionFilter>();
});
🔥 Real-World Use Cases
Here are some common use cases where Action Filters shine:
⏱ Measuring request duration
🔒 Enforcing custom authorization
🧪 Validating models before actions run
🪵 Logging request metadata
❌ Short-circuiting execution based on condition (e.g., maintenance mode)
✍️ Best Practices
✅ Keep filters small and focused
✅ Don’t mix concerns (e.g., logging + auth in one filter)
✅ Use dependency injection in filters via constructors
✅ Prefer IAsyncActionFilter
for async logic
✅ Avoid abusing filters for things that belong in middleware
🚀 Wrapping Up
Action Filters are a clean and powerful way to inject behavior around your controller actions without bloating your business logic.
Once you start using them, you’ll wonder how you managed without them.
I hope you found this guide helpful and informative.
Thanks for reading!
If you enjoyed this article, feel free to share it and follow me for more practical, developer-friendly content like this.