# How Filters Are Implemented and Applied in ASP.NET Core: Architecture and Pipeline

> Discover how ASP.NET Core implements filters through its pluggable pipeline and IFilterProvider architecture. Learn about filter factories and execution for actions and minimal APIs, optimizing your web applications.

- Repository: [.NET Platform/aspnetcore](https://github.com/dotnet/aspnetcore)
- Tags: architecture
- Published: 2026-07-13

---

**ASP.NET Core implements filters through a pluggable pipeline where `IFilterProvider` implementations discover filter metadata, `FilterFactory` caches and materializes instances, and `ControllerActionInvoker` executes them in ordered stages around actions, while minimal APIs use `IEndpointFilter` with `RequestDelegateFilterPipelineBuilder` for lightweight per-endpoint interception.**

The filter system in the `dotnet/aspnetcore` repository provides a cross-cutting concern mechanism that intercepts requests for both MVC controllers and minimal API endpoints. Understanding how filters are implemented and applied in ASP.NET Core requires examining the interplay between filter metadata interfaces, provider abstractions, and the execution pipeline built by the framework.

## Core Filter Components

The filter architecture centers on several key contracts and implementations located in the MVC abstractions and core layers.

**Filter Metadata Interfaces** define the capability contracts that concrete filters implement. Located in `src/Mvc/Mvc.Abstractions/src/Filters/*.cs`, these include:

- **`IFilterMetadata`** – The base interface that all filters must implement.
- **`IActionFilter`** and **`IAsyncActionFilter`** – For running logic before and after action methods.
- **`IResultFilter`** – For intercepting action results.
- **`IExceptionFilter`** – For handling unhandled exceptions.
- **`IAuthorizationFilter`** – For authorization checks.

**`IFilterProvider`** defines the contract for discovering filters for a given request. The default implementation at [`src/Mvc/Mvc.Core/src/Filters/DefaultFilterProvider.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Filters/DefaultFilterProvider.cs) populates `FilterItem.Filter` for each `FilterDescriptor` by resolving instances from attributes, global registration, or custom sources.

**`FilterFactory`** at [`src/Mvc/Mvc.Core/src/Filters/FilterFactory.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Filters/FilterFactory.cs) serves as the central hub. It coordinates all providers, orders the filters using `FilterDescriptorOrderComparer`, caches reusable filters, and creates the final `IFilterMetadata[]` array used at runtime.

## The Filter Implementation Pipeline

The process of how filters are implemented and applied follows a strict lifecycle across five distinct stages.

### Discovery

When a request targets a controller action or minimal API endpoint, ASP.NET Core creates an `ActionContext` (or `EndpointBuilder`). The framework identifies candidate filters from attributes applied to controllers, actions, or global registration in [`Startup.cs`](https://github.com/dotnet/aspnetcore/blob/main/Startup.cs).

### Provider Execution

All registered `IFilterProvider` implementations (by default only `DefaultFilterProvider`) run `OnProvidersExecuting`. For each `FilterDescriptor`, the provider calls `DefaultFilterProvider.ProvideFilter`:

- If the descriptor references a concrete filter instance, it is stored directly in the `FilterItem`.
- If it implements `IFilterFactory`, the factory creates the filter instance, and the provider marks it reusable based on the `IsReusable` property.

The `FilterProviderContext` at [`src/Mvc/Mvc.Abstractions/src/Filters/FilterProviderContext.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Abstractions/src/Filters/FilterProviderContext.cs) carries the `ActionContext` and mutable list of `FilterItem`s, allowing providers to add, replace, or remove filters dynamically.

### Caching

If every static filter is reusable and only the default provider is present, the resulting filter array is cached on the `ActionDescriptor` via `CachedReusableFilters`. This avoids re-instantiating filters on every request, significantly improving performance.

### Invocation

The `ControllerActionInvoker` at [`src/Mvc/Mvc.Core/src/Infrastructure/ControllerActionInvoker.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Infrastructure/ControllerActionInvoker.cs) iterates through the resolved filter list, executing them in the following pipeline stages:

1. **Authorization** – `IAuthorizationFilter` instances run first.
2. **Resource** – `IResourceFilter` logic executes (if present).
3. **Action** – `IActionFilter` or `IAsyncActionFilter` methods run before and after the action.
4. **Exception** – `IExceptionFilter` handles unhandled exceptions.
5. **Result** – `IResultFilter` executes around the result processing.
6. **Always-run result** – Final result filters guaranteed to execute.

For async filters, each stage awaits the filter’s `InvokeAsync` method, allowing pre- and post-processing around the inner delegate.

## MVC Action Invocation vs. Minimal API Endpoints

ASP.NET Core provides two distinct filter implementations depending on the programming model.

### MVC Action Invoker Pipeline

For controller-based MVC applications, the `ControllerActionInvoker` builds a nested delegate chain. Each filter implements `On[Stage]Executing` and `On[Stage]Executed` (or async variants), creating a Russian doll pattern around the actual action method execution. The invoker respects execution order defined by `IOrderedFilter` and scopes (global → controller → action).

### Minimal API Endpoint Filters

For minimal API route handlers, the same concept uses **`IEndpointFilter`** defined in [`src/Http/Http.Abstractions/src/IEndpointFilter.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Http/Http.Abstractions/src/IEndpointFilter.cs). The pipeline is constructed by `RequestDelegateFilterPipelineBuilder` at [`src/Http/Routing/src/RequestDelegateFilterPipelineBuilder.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Http/Routing/src/RequestDelegateFilterPipelineBuilder.cs).

When you call `AddEndpointFilter`, the builder stores a list of `EndpointFilterFactory` delegates. The `Create` method builds a pipeline by wrapping the original `RequestDelegate` with these factories, producing a `RequestDelegate` that runs the filters before invoking the handler.

## Ordering, Caching, and Reusability

Three critical design patterns govern filter behavior.

**Execution Order** – Filters implement `IOrderedFilter` (default order = 0). The `FilterDescriptorOrderComparer` sorts them so that global filters run before controller-level, which run before action-level filters. Lower order values execute first.

**Filter Reusability** – Stateless filters can mark `IsReusable = true`. Reusable filters are cached and shared across requests, reducing allocation pressure. The `FilterFactory` checks this flag when building the final filter array.

**Filter Containers** – Filters implementing `IFilterContainer` receive a reference to their original descriptor (`FilterDefinition`), enabling later introspection of filter metadata during execution.

## Practical Implementation Examples

The following examples demonstrate how to implement filters in both MVC and minimal API contexts.

### Async Action Filter for MVC

```csharp
public class LogActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context,
                                              ActionExecutionDelegate next)
    {
        Console.WriteLine($"Executing {context.ActionDescriptor.DisplayName}");
        var resultContext = await next();   // invoke the action
        Console.WriteLine($"Executed {context.ActionDescriptor.DisplayName}");
    }
}

```

Register globally so `DefaultFilterProvider` resolves it via DI:

```csharp
services.AddControllers(options =>
{
    options.Filters.Add<LogActionFilter>();   // resolved via DI at request time
});

```

### Endpoint Filter for Minimal APIs

```csharp
app.MapGet("/hello", async (HttpContext ctx) =>
{
    await ctx.Response.WriteAsync("Hello");
})
.AddEndpointFilter(async (context, next) =>
{
    Console.WriteLine("Before handler");
    var result = await next(context);
    Console.WriteLine("After handler");
    return result;
});

```

Behind the scenes, `RequestDelegateFilterPipelineBuilder.Create` composes this factory into the final request delegate.

## Summary

- **Filter metadata interfaces** (`IActionFilter`, `IAsyncActionFilter`, etc.) define contracts in `src/Mvc/Mvc.Abstractions/src/Filters/`.
- **`DefaultFilterProvider`** discovers and materializes filters from descriptors.
- **`FilterFactory`** orchestrates provider execution, ordering, and caching via `CachedReusableFilters`.
- **Execution order** follows global → controller → action scope, controlled by `IOrderedFilter` and `FilterDescriptorOrderComparer`.
- **Reusability** eliminates per-request allocation when `IsReusable` is true.
- **Minimal APIs** use `IEndpointFilter` with `RequestDelegateFilterPipelineBuilder` for lightweight interception without the full MVC stack.

## Frequently Asked Questions

### How does ASP.NET Core determine the order of filter execution?

Filters implement `IOrderedFilter` and specify an `Order` property (default 0). The `FilterDescriptorOrderComparer` sorts them by scope—global filters execute first, followed by controller-level, then action-level filters. Within the same scope, lower order values execute before higher values.

### What is the difference between IActionFilter and IAsyncActionFilter?

`IActionFilter` provides synchronous `OnActionExecuting` and `OnActionExecuted` methods, while `IAsyncActionFilter` exposes a single `OnActionExecutionAsync` method that accepts a delegate to invoke the next filter or action. Async filters prevent thread-blocking during I/O operations and are preferred for modern ASP.NET Core applications.

### Can filters be reused across multiple requests to improve performance?

Yes. If a filter implements `IFilterFactory` and sets `IsReusable = true`, or if it is a direct instance registered as a singleton, the `FilterFactory` caches it in `CachedReusableFilters` on the `ActionDescriptor`. This prevents reinstantiation on every request, reducing garbage collection pressure.

### How do minimal API endpoint filters differ from MVC action filters?

Minimal API endpoint filters implement `IEndpointFilter` and use `RequestDelegateFilterPipelineBuilder` to wrap the request delegate in a functional pipeline. Unlike MVC filters, they do not use the `ControllerActionInvoker` or support the full filter stage pipeline (authorization, resource, exception, result). They provide lightweight interception specifically for the minimal API routing model.