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:

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

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:

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:

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

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

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

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:

Real-World Scenario

You want any unexpected exception in your API to:

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

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

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.