ASP.NET Core  

6 Types of Filters in ASP.NET Core – Complete Guide (With Examples & Use Cases)

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:

  • “Is this user allowed to call this action at all?”

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))
    • If the user is not authenticated OR not in the required role, we treat them as unauthorized.

  • 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:

  • The Resource Filter starts the stopwatch

  • The action executes

  • The filter logs how long it took

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:

  • Logging input parameters

  • Validating custom conditions

  • Modifying action results

  • Timing the action itself (not the whole request)

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:

  • Action name

  • Parameter names and values

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.

    • context.ActionArguments is a dictionary of parameter name → value.

    • We loop over it and print each parameter.

  • 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:

  • Validate input model

  • Log requests for a specific endpoint

  • Enforce simple authorization logic without full MVC

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.

    • context gives access to HttpContext and route arguments.

    • next is a delegate that represents the next filter or the endpoint itself.

  • 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:

  • Log before the endpoint

  • Execute the handler

  • Log after the endpoint finishes

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:

  • Adding headers

  • Wrapping responses in a standard format

  • Conditionally formatting output

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.

    • We access context.HttpContext.Response.Headers and add a custom header.

  • 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.

    • Here, we just log to the console; in a real project, you might log response status or duration.

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:

  1. Authorization Filters: Can stop unauthorized requests early.

  2. Resource Filters: Run around the entire MVC execution (good for caching/timing).

  3. Action Filters: Run before and after the controller action.

  4. Result Filters: Run around the result execution (e.g., views, JSON).

  5. Exception Filters: Triggered whenever an unhandled exception occurs inside MVC.

  6. Endpoint Filters: Special case for Minimal APIs, wrapping a single endpoint.