In ASP.NET Core, filters give you a powerful way to inject logic at specific points in the MVC / Web API request pipeline. Instead of repeating the same code in every controller action (like logging, authorization checks, exception handling, or response wrapping), you can move that logic into reusable filters.
In this article, we’ll go through each filter type in detail, with:
What it does in the pipeline
A realistic use case (e.g., admin check, performance timing, global error handling)
A small code example
A line-by-line explanation so you can understand the syntax clearly
1. Authorization Filters
What is an Authorization Filter?
An Authorization Filter is the first filter that runs in the MVC pipeline. It executes before model binding and before the action, which makes it the ideal place to decide:
If the user is not authorized, the filter can short-circuit the pipeline by returning a ForbidResult or UnauthorizedResult instead of letting the action execute.
Real-World Scenario
Imagine you have an Admin Dashboard where only users with the Admin role should be able to access. Instead of adding if !User.IsInRole("Admin")) ... in every action, you can create one authorization filter and apply it once.
Example – Custom Role Authorization Filter
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
public class RoleAuthorizationFilter : IAuthorizationFilter
{
private readonly string _role;
public RoleAuthorizationFilter(string role)
{
_role = role;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (!user.Identity?.IsAuthenticated ?? true || !user.IsInRole(_role))
{
context.Result = new ForbidResult();
}
}
}
Line-by-Line Explanation
RoleAuthorizationFilter : IAuthorizationFilter
This class implements IAuthorizationFilter, which means ASP.NET Core will call its OnAuthorization method during the authorization stage.
Private field _role and constructor
We inject a role name (e.g., "Admin") via the constructor so this filter can be reused for multiple roles.
OnAuthorization(AuthorizationFilterContext context)
This method is called automatically by the framework. The context gives access to the current HttpContext, the user, the route data, etc.
var user = context.HttpContext.User;
Gets the current logged-in user (principal).
Condition
if (!user.Identity?.IsAuthenticated ?? true || !user.IsInRole(_role))
context.Result = new ForbidResult();
Setting context.Result tells ASP.NET Core:
“Do not continue the action. Return this result immediately.”
In this case, the user receives an HTTP 403 Forbidden.
Applying the Filter to a Controller
[ApiController]
[Route("api/[controller]")]
[TypeFilter(typeof(RoleAuthorizationFilter), Arguments = new object[] { "Admin" })]
public class AdminController : ControllerBase
{
[HttpGet("dashboard")]
public IActionResult GetDashboard()
{
return Ok("This is the admin dashboard.");
}
}
Here:
[TypeFilter] tells ASP.NET Core to resolve RoleAuthorizationFilter from DI and pass "Admin" to its constructor.
Any request to /api/admin/dashboard must be authenticated and in the "Admin" role; otherwise, the action never runs.
2. Resource Filters
What is a Resource Filter?
A Resource Filter runs after authorization but before model binding and action execution. It also runs again after the action and result are done.
It’s perfect for “outer-layer” concerns like:
Measuring how long a request took
Implementing custom caching
Preventing further processing if some header or condition is not met
Real-World Scenario
Suppose you want to know how long your API actions are taking in production. Instead of adding Stopwatch In every action, you create one Resource Filter and apply it globally or per controller.
Example – Request Timing Resource Filter
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
public class RequestTimingResourceFilter : IResourceFilter
{
private Stopwatch _stopwatch;
public void OnResourceExecuting(ResourceExecutingContext context)
{
_stopwatch = Stopwatch.StartNew();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
_stopwatch.Stop();
var elapsedMs = _stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Request took {elapsedMs} ms");
}
}
Line-by-Line Explanation
RequestTimingResourceFilter : IResourceFilter
Implements IResourceFilter, so ASP.NET Core knows to call its resource stage methods.
Private field _stopwatch
Stores the Stopwatch instance for a single request.
OnResourceExecuting(ResourceExecutingContext context)
Called before model binding and before the action method.
We start the stopwatch here: Stopwatch.StartNew().
OnResourceExecuted(ResourceExecutedContext context)
Called after the action and result have been executed.
We stop the timer, get the elapsed milliseconds, and log to the console.
Applying It to a Controller
[ApiController]
[Route("api/[controller]")]
[TypeFilter(typeof(RequestTimingResourceFilter))]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
return Ok(new { Id = id, Name = "Sample Product" });
}
}
Now every time /api/products/{id} is called:
In a real project, you might log to Application Insights, Seq, or a database.
3. Action Filters
What is an Action Filter?
An Action Filter runs right before and right after the action method. This makes it ideal for logic closely tied to the action itself, such as:
Real-World Scenario
Imagine you want to log every request’s incoming data for auditing. Instead of logging inside each controller action, you use an Action Filter that logs:
Example – Log Action Arguments Filter
using Microsoft.AspNetCore.Mvc.Filters;
public class LogActionArgumentsFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
foreach (var arg in context.ActionArguments)
{
Console.WriteLine($"Param: {arg.Key} = {arg.Value}");
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Optional: log something after the action executes
Console.WriteLine("Action executed.");
}
}
Line-by-Line Explanation
LogActionArgumentsFilter : IActionFilter
Implements IActionFilter, which has two methods that correspond to “before action” and “after action”.
OnActionExecuting(ActionExecutingContext context)
Runs right before the action.
OnActionExecuted(ActionExecutedContext context)
Runs right after the action method.
You could log the result, check for exceptions, etc.
Here, we just print "Action executed.".
Applying It to a Users Controller
[ApiController]
[Route("api/[controller]")]
[TypeFilter(typeof(LogActionArgumentsFilter))]
public class UsersController : ControllerBase
{
[HttpPost("create")]
public IActionResult CreateUser([FromBody] UserDto user)
{
return Ok($"User {user.Name} created.");
}
}
public record UserDto(string Name, int Age);
When a client hits /api/users/create With a JSON body, the filter logs the parameters (e.g., Name and Age) before the action runs.
4. Endpoint Filters (for Minimal APIs)
What is an Endpoint Filter?
Endpoint Filters were introduced in .NET 7 for Minimal APIs. Unlike MVC filters, they are attached directly to a single endpoint, and they behave like a mini–middleware that wraps just that route.
Real-World Scenario
In a Minimal API application, you might want to:
Example – Simple Logging Endpoint Filter
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Metadata;
public class SimpleLoggingFilter : IEndpointFilter
{
public async ValueTask<object> InvokeAsync(
EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
var endpointName = context.HttpContext.GetEndpoint()?.DisplayName;
Console.WriteLine($"Calling endpoint: {endpointName}");
var result = await next(context);
Console.WriteLine($"Endpoint completed: {endpointName}");
return result;
}
}
Line-by-Line Explanation
SimpleLoggingFilter : IEndpointFilter
Implements IEndpointFilter, the interface used by Minimal API filters.
InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
This is similar to middleware’s InvokeAsync.
endpointName = context.HttpContext.GetEndpoint()?.DisplayName;
Reads the current endpoint’s display name for logging.
Log "Calling endpoint" before next(context)
Runs before the actual endpoint handler.
var result = await next(context);
Calls the next filter or the endpoint and waits for completion.
Log "Endpoint completed" after the call
Runs after the handler; then we return the result.
Attaching It to a Minimal API Route
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => Results.Ok("Hello from Minimal API!"))
.AddEndpointFilter<SimpleLoggingFilter>();
app.Run();
Every call to /hello will now:
5. Exception Filters
What is an Exception Filter?
An Exception Filter runs when an unhandled exception occurs during:
Action execution
Result execution
Real-World Scenario
You want any unexpected exception in your API to:
Be logged (e.g., in Application Insights, Serilog)
Return a clean JSON error response, not a raw stack trace
Example – Simple Exception Filter
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
public class SimpleExceptionFilter : IExceptionFilter
{
private readonly ILogger<SimpleExceptionFilter> _logger;
public SimpleExceptionFilter(ILogger<SimpleExceptionFilter> logger)
{
_logger = logger;
}
public void OnException(ExceptionContext context)
{
_logger.LogError(context.Exception, "Unhandled exception");
var result = new ObjectResult(new { Message = "An unexpected error occurred." })
{
StatusCode = StatusCodes.Status500InternalServerError
};
context.Result = result;
context.ExceptionHandled = true;
}
}
Line-by-Line Explanation
SimpleExceptionFilter : IExceptionFilterImplements IExceptionFilter so it will be called when an exception is thrown.
Constructor injects ILogger<SimpleExceptionFilter>Allows structured logging of exceptions.
OnException(ExceptionContext context)Triggered whenever an unhandled exception happens inside a controller action or result.
_logger.LogError(context.Exception, "Unhandled exception");Logs the full exception stack with a custom message.
new ObjectResult(new { Message = "An unexpected error occurred." })Creates a standard JSON error response with a friendly message.
StatusCode = 500Sets HTTP status to 500 Internal Server Error.
context.Result = result; context.ExceptionHandled = true;
Tells ASP.NET Core:
“We’ve handled this exception. Use this result instead. Don’t re-throw it.”
Registering It Globally
builder.Services.AddControllers(options =>
{
options.Filters.Add<SimpleExceptionFilter>();
});
Now all controllers share the same exception handling logic.
6. Result Filters
What is a Result Filter?
Result Filters run after the action method has returned a result, but before and after the result is executed (for example, before a view is rendered or JSON is written to the response).
They are ideal for scenarios where you want to modify or inspect the final response, such as:
Real-World Scenario
Your API must include a header like X-App-Version or X-Filtered in all responses from a certain controller. Instead of adding this in each action, you create a Result Filter.
Small Example – Custom Header Result Filter
using Microsoft.AspNetCore.Mvc.Filters;
public class CustomHeaderResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Append("X-Custom-Header", "Filtered");
}
public void OnResultExecuted(ResultExecutedContext context)
{
// Optional: do something after the response is sent
Console.WriteLine("Response was sent to the client.");
}
}
Line-by-Line Explanation
CustomHeaderResultFilter : IResultFilter
Implements IResultFilter to run around the result execution.
OnResultExecuting(ResultExecutingContext context)
Called before the result is executed.
Headers.Append("X-Custom-Header", "Filtered");
Adds a header X-Custom-Header: Filtered to the HTTP response.
OnResultExecuted(ResultExecutedContext context)
Called after the result has been executed.
Applying It to a Status Controller
[ApiController]
[Route("api/[controller]")]
[TypeFilter(typeof(CustomHeaderResultFilter))]
public class InfoController : ControllerBase
{
[HttpGet("status")]
public IActionResult GetStatus()
{
return Ok(new { Status = "Running" });
}
}
Now, every response from /api/info/status will include the custom header.
Putting It All Together – Filter Order
To understand how everything plays together, here’s the rough order for a typical MVC request:
Authorization Filters: Can stop unauthorized requests early.
Resource Filters: Run around the entire MVC execution (good for caching/timing).
Action Filters: Run before and after the controller action.
Result Filters: Run around the result execution (e.g., views, JSON).
Exception Filters: Triggered whenever an unhandled exception occurs inside MVC.
Endpoint Filters: Special case for Minimal APIs, wrapping a single endpoint.