ASP.NET Core Filters Explained in C
Filters in ASP.NET Core allow developers to run code before or after specific stages in the request processing pipeline. This article provides a clear overview of how filters work, the different types of filters available in the framework, their execution order, and practical C# implementation examples for handling cross-cutting concerns such as logging, validation, and error handling.
Understanding Filters
In ASP.NET Core, filters run within the MVC action invocation pipeline, often referred to as the filter pipeline. The runtime selects the action method to execute and then invokes the filters associated with that action.
Unlike middleware, which handles requests across the entire
application pipeline, filters have access to MVC context, such as the
HttpContext, ModelState, and the specific
controller and action metadata being invoked. This makes them ideal for
handling cross-cutting concerns without duplicating code across multiple
controllers or actions.
Types of Filters
ASP.NET Core provides five primary filter types, each executing at a distinct stage in the pipeline:
- Authorization Filters: Run first to determine whether the current user is authorized to execute the requested action. They short-circuit the pipeline if the request fails authorization.
- Resource Filters: Execute immediately after authorization filters and wrap the rest of the pipeline. They are useful for tasks like caching or performance profiling, where you might want to short-circuit the pipeline before model binding occurs.
- Action Filters: Run immediately before and after an individual action method executes. They can inspect and modify action arguments, manipulate the action result, or prevent the action from executing.
- Exception Filters: Execute when an unhandled exception occurs during the creation of a controller, model binding, action filters, or action methods. They apply global exception-handling policies.
- Result Filters: Run immediately before and after the execution of the action result (such as serializing JSON or rendering a view). They are ideal for modifying HTTP headers or response formatting.
Filter Execution Order
Filters of the same type run in a specific order based on their assigned scope:
- Global scope: Applies to every action across the entire application.
- Controller scope: Applies to all actions within a specific controller.
- Action scope: Applies strictly to the designated action method.
During request processing, outer scopes (Global, then Controller, then Action) run their "before" code first. The "after" code runs in reverse order (Action, Controller, Global).
Implementing an Action Filter in C
You can implement filters synchronously by implementing
IActionFilter or asynchronously by implementing
IAsyncActionFilter.
Here is an example of an asynchronous action filter written in C#:
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
public class LoggingActionFilter : IAsyncActionFilter
{
private readonly ILogger<LoggingActionFilter> _logger;
public LoggingActionFilter(ILogger<LoggingActionFilter> logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Code executed before the action method
var stopwatch = Stopwatch.StartNew();
_logger.LogInformation("Executing action: {ActionName}", context.ActionDescriptor.DisplayName);
// Execute the next filter or the action itself
ActionExecutedContext resultContext = await next();
// Code executed after the action method
stopwatch.Stop();
_logger.LogInformation("Action {ActionName} completed in {ElapsedMilliseconds}ms",
context.ActionDescriptor.DisplayName, stopwatch.ElapsedMilliseconds);
}
}Applying Filters
Filters can be applied using different approaches depending on the required scope and whether dependency injection is needed:
1. Global Registration
Registered inside Program.cs to apply to all
endpoints:
builder.Services.AddControllers(options =>
{
options.Filters.Add<LoggingActionFilter>();
});2. ServiceFilter Attribute
When a filter depends on services injected via dependency injection,
apply it at the controller or action level using
[ServiceFilter]:
[ServiceFilter(typeof(LoggingActionFilter))]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
return Ok(new { Id = id, Name = "Jane Doe" });
}
}Ensure the filter is registered in the dependency injection container:
builder.Services.AddScoped<LoggingActionFilter>();3. Attribute Implementation
If a filter does not require constructor dependency injection,
inherit from ActionFilterAttribute to apply it directly as
an attribute:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
[ValidateModel]
[HttpPost]
public IActionResult Create([FromBody] UserModel model)
{
return CreatedAtAction(nameof(Create), model);
}Filters keep controllers clean and focused strictly on application logic by extracting common HTTP-handling tasks into reusable, maintainable components.