Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
ASP.NET Core MVC filters add reusable behavior around controller actions and other MVC stages. They are useful for authorization-related checks, request timing, validation, caching, exception translation, response headers, and short-circuiting an action before it runs.
This guide targets ASP.NET Core 5, not the separate legacy ASP.NET MVC 5 framework. ASP.NET Core 5 is unsupported as of August 18, 2026, so the examples use its Startup-based hosting model and should be treated as version-specific maintenance guidance.
What is an MVC filter?
An MVC filter is a reusable component that runs at a defined point in the ASP.NET Core MVC action pipeline. It can inspect the request, controller, action arguments, model state, exceptions, or action result; perform work before and after a stage; or stop later MVC processing by assigning a result.
Filters are not LINQ or data filters. They do not filter rows from a collection. They participate in the HTTP and MVC execution lifecycle.
#1 Best Overall
Where filters run
The simplified pipeline is:
Middleware
→ Routing and action selection
→ Authorization filters
→ Resource filters
→ Model binding
→ Action filters
→ Controller action
→ Exception filters
→ Result filters
→ Action-result execution
→ Resource filters unwind
→ Middleware unwinds
Authorization filters run first and determine whether the request is allowed. Resource filters run after authorization and before model binding. Action filters surround action-method execution. Exception filters can handle eligible exceptions raised during MVC action, filter, or result execution. Result filters surround successful action-result execution. Middleware surrounds MVC more broadly and runs at a lower level.
For the official pipeline details, see Microsoft’s ASP.NET Core filters documentation.
Choose the right mechanism
| Requirement | Preferred mechanism |
|---|---|
| Require a role or policy | [Authorize] and authorization policies |
| Run before model binding | Resource filter |
| Inspect or validate action arguments | Action filter |
| Measure an MVC action | Action filter |
| Convert an MVC exception into an MVC result | Exception filter |
| Add headers around a successful MVC result | Result filter |
| Handle exceptions across the application | Exception-handling middleware |
| Apply behavior to static files or non-MVC endpoints | Middleware |
| Filter Minimal API handlers | Endpoint filters in newer ASP.NET Core versions, not ASP.NET Core 5 MVC filters |
Use filters when you need MVC-specific information such as the selected action, action arguments, model state, or IActionResult. Use middleware for broad request behavior, global exception handling, correlation IDs, or processing that must also cover non-MVC endpoints.
Free tools Windows power users keep installed
One-click scans. No signup required.
Create a basic action filter
For a concise attribute-based filter, derive from ActionFilterAttribute:
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
public sealed class RequestTimingFilterAttribute : ActionFilterAttribute
{
private readonly Stopwatch _stopwatch = new Stopwatch();
public override void OnActionExecuting(ActionExecutingContext context)
{
_stopwatch.Start();
}
public override void OnActionExecuted(ActionExecutedContext context)
{
_stopwatch.Stop();
Console.WriteLine($"{context.ActionDescriptor.DisplayName} took " +
$"{_stopwatch.ElapsedMilliseconds} ms.");
}
}
Apply it to one action:
[RequestTimingFilter]
public IActionResult Details(int id)
{
return View(id);
}
Or apply it to a controller:
[RequestTimingFilter]
public class ProductsController : Controller
{
}
For production code, prefer an injected logger or metrics service over Console.WriteLine. Also note that ASP.NET Core’s ActionFilterAttribute implements both action-filter and result-filter interfaces. Do not assume that every subclass affects only the action stage.
Use asynchronous filters for I/O
Database, network, and other asynchronous operations should use IAsyncActionFilter rather than blocking with .Result or .Wait():
using Microsoft.AspNetCore.Mvc.Filters;
public sealed class AuditFilter : IAsyncActionFilter
{
private readonly IAuditWriter _auditWriter;
public AuditFilter(IAuditWriter auditWriter)
{
_auditWriter = auditWriter;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
await _auditWriter.WriteAsync(
$"Starting {context.ActionDescriptor.DisplayName}");
ActionExecutedContext executedContext = await next();
await _auditWriter.WriteAsync(
$"Finished {context.ActionDescriptor.DisplayName}");
// Inspect executedContext.Exception or executedContext.Result here.
}
}
Calling next() allows subsequent filters and the action to execute. If the filter does not call it, the action is short-circuited.
Short-circuit an action
Assign context.Result and return when a request should not proceed:
Rank #2
public sealed class RequireHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuting(
ActionExecutingContext context)
{
if (!context.HttpContext.Request.Headers.ContainsKey("X-Tenant"))
{
context.Result = new BadRequestObjectResult(
new { error = "X-Tenant header is required." });
}
}
}
The asynchronous equivalent is:
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
if (!context.HttpContext.Request.Headers.ContainsKey("X-Tenant"))
{
context.Result = new BadRequestObjectResult(
new { error = "X-Tenant header is required." });
return;
}
await next();
}
A short-circuiting authorization or resource filter prevents later MVC stages from running. Ordinary result filters do not necessarily run in that situation. A result filter can cancel action-result execution, but it should provide an appropriate response. Headers and status codes generally cannot be changed after the response has started.
Register filters in ASP.NET Core 5
Global registration with Startup
ASP.NET Core 5 uses Startup, not the later WebApplication.CreateBuilder hosting model:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<AuditFilter>();
services.AddControllersWithViews(options =>
{
options.Filters.Add<AuditFilter>();
});
}
This adds the filter to the entire MVC application. An instance can also be added:
services.AddControllersWithViews(options =>
{
options.Filters.Add(new RequestTimingFilterAttribute());
});
Be careful with instance registration. A filter instance supplied directly to Add can be reused like a singleton. Mutable fields, such as a request stopwatch, can then create thread-safety and cross-request bugs. Prefer type-based registration and dependency injection for filters with state or services.
Action and controller registration
A filter without dependencies can be applied directly as an attribute:
[RequestTimingFilter]
public IActionResult Details(int id) => View(id);
For a filter resolved from dependency injection, register it first and use ServiceFilter:
services.AddScoped<AuditFilter>();
[ServiceFilter(typeof(AuditFilter))]
public IActionResult Create() => View();
ServiceFilterAttribute requires the filter type to be registered. TypeFilterAttribute is often more convenient when the filter itself is not registered as a service:
[TypeFilter(typeof(AuditFilter))]
public IActionResult Create() => View();
Both attributes support constructor dependencies, but explicit registration is usually clearer for application-wide filters.
Dependency injection and lifetimes
Inject dependencies through the constructor rather than resolving services manually:
public sealed class TenantFilter : IAsyncActionFilter
{
private readonly ITenantResolver _tenantResolver;
public TenantFilter(ITenantResolver tenantResolver)
{
_tenantResolver = tenantResolver;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
var tenant = await _tenantResolver.ResolveAsync(context.HttpContext);
if (tenant == null)
{
context.Result = new NotFoundResult();
return;
}
await next();
}
}
services.AddScoped<ITenantResolver, TenantResolver>();
services.AddScoped<TenantFilter>();
A filter that depends on request-scoped services must not be treated as a singleton. Avoid reusable filters with request-specific mutable fields, static request state, or manually constructed dependencies.
Filter scope and execution order
By default, filters nest in this order:
- Global filters
- Controller filters
- Action filters
Before methods run from the outside inward, while after methods unwind in reverse order:
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Global before
Controller before
Action before
Action method
Action after
Controller after
Global after
Implement IOrderedFilter, or set the Order property on an attribute, when explicit ordering is necessary:
public sealed class OrderedAuditFilter : ActionFilterAttribute
{
public OrderedAuditFilter()
{
Order = 10;
}
}
Lower order values run first on the way in and last on the way out. Use extreme values sparingly because ordering across multiple libraries quickly becomes difficult to maintain.
Authorization filters
Authentication establishes who the caller is. Authorization decides whether that identity may perform an operation.
For normal role- and policy-based authorization, use the built-in authorization system:
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →[Authorize(Policy = "CanEditProducts")]
public IActionResult Edit(int id)
{
return View(id);
}
Do not create a custom authorization filter merely to duplicate a policy check. Use authorization policies and custom policy handlers instead. Authorization filters have a before stage but no corresponding after stage, and exceptions thrown there are not handled by exception filters.
Resource filters
Resource filters run after authorization and before model binding. They are useful when processing should stop before expensive MVC work begins, such as a cache lookup, a request-level precondition, or a large-upload scenario where form-value model binding must be disabled.
They are not the default choice for ordinary action validation. Use an action filter when model binding and action arguments should already be available.
Exception filters
An exception filter can translate a known MVC exception into an action result:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
public sealed class DomainExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.Exception is ProductNotFoundException)
{
context.Result = new NotFoundObjectResult(
new { error = context.Exception.Message });
context.ExceptionHandled = true;
}
}
}
Exception filters cover exceptions raised during MVC action, filter, or result execution. They do not replace exception-handling middleware and should not be expected to handle failures from earlier middleware, routing, or other pipeline stages. Use an exception filter when the response genuinely depends on the selected MVC controller or action; otherwise prefer UseExceptionHandler or other exception-handling middleware.
Result filters
Result filters surround execution of an IActionResult. They are suitable for behavior tied to MVC result execution, such as adding a response header:
public sealed class CorrelationHeaderFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers["X-Correlation-Id"] =
context.HttpContext.TraceIdentifier;
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
Set headers in OnResultExecuting, before the response starts. Code in OnResultExecuted may run too late to change headers or the body. Ordinary result filters do not always run after authorization or resource short-circuiting, or after every exception path. If behavior must run for every MVC result, investigate IAlwaysRunResultFilter or IAsyncAlwaysRunResultFilter.
Middleware versus filters
Use middleware for static files, WebSockets, non-MVC endpoints, requests before action selection, broad request logging, correlation IDs, and global exception handling. Middleware does not directly understand action arguments, model binding, controller metadata, or MVC results.
Recommended Free Tools
Use a filter for MVC-specific lifecycle behavior. A controller base class may be better when behavior is tightly coupled to shared controller methods. A service decorator is usually better for application concerns such as repository retries, domain caching, transaction boundaries, or auditing an operation independently of HTTP.
Best Value
- Applying all key ASP.NET Core components, including MVC for HTML generation, .NET Core, EF Core, ASP.NET Identity, dependency injection, and more
- Integrating ASP.NET Core with leading client-side frameworks, including Bootstrap
- ASP.NET Core code for implementing business logic and data transformations
- Handling configuration, routing, controllers, views, and common tasks (including posting forms and presenting data)
- Performing complementary tasks: error handling, logging, application design, authentication, localization, and more
Common problems and fixes
The filter never runs
- Confirm the filter is registered with
AddControllersWithViewsorAddControllers. - Confirm the request is handled by MVC and reaches the expected controller action.
- Check attribute placement and namespace imports.
- Check whether middleware or an earlier filter short-circuits the request.
- Do not attach MVC action filters to Razor Pages handler methods; Razor Pages use page-filter interfaces.
Dependency injection fails
- Register the filter and every constructor dependency.
- Do not use
ServiceFilterwithout registering the filter type. - Do not manually instantiate a filter that has dependencies.
- Check for a singleton filter depending on a scoped service.
The action does not execute
Look for an assigned context.Result, authorization failure, resource-filter cache hit, failed header or model-state validation, or an exception in an earlier filter.
A response header cannot be changed
Headers must be modified before the response starts. Move the code from OnResultExecuted to OnResultExecuting, or use middleware at the appropriate stage.
A custom model-validation filter is redundant
API controllers marked with [ApiController] automatically produce a 400 response for invalid model state. Add a custom filter only when the application needs behavior beyond that default.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsTesting a filter
Test filters independently with mocked dependencies and MVC context objects. At minimum, verify that:
- the injected dependency is called;
- the action proceeds when validation succeeds;
- the action delegate is not called after short-circuiting;
- the expected result and status code are assigned;
- only intended exceptions are translated; and
- multiple filters execute in the documented order.
A useful short-circuit test sets up a request without X-Tenant, invokes the filter, and asserts that the result is BadRequestObjectResult while the action delegate was never invoked.
ASP.NET Core 5 versus current ASP.NET Core
Do not copy current minimal-hosting examples into an ASP.NET Core 5 application. ASP.NET Core 5 uses Startup.ConfigureServices and Startup.Configure; later applications may use WebApplication.CreateBuilder. MVC filters remain distinct from endpoint filters used by newer Minimal API applications.
For upgraded applications, consult the documentation for the exact target framework and verify hosting, filter, authorization, and endpoint APIs before migrating examples.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

