Skip to main content

Command Palette

Search for a command to run...

ASP.NET Core Middleware vs Action Filters vs Endpoint Filters: Enterprise Cross-Cutting Concern Placement Guide

Published
β€’11 min read
ASP.NET Core Middleware vs Action Filters vs Endpoint Filters: Enterprise Cross-Cutting Concern Placement Guide

Every enterprise ASP.NET Core application accumulates cross-cutting concerns β€” logging, authentication enforcement, request throttling, correlation ID propagation, response shaping, audit trails. The question teams argue about is: where does each concern live? In middleware? In an action filter? In an endpoint filter? Getting this wrong costs you testability, observability, and the quiet maintenance debt that shows up during postmortems.

Want implementation-ready .NET source code you can adapt fast? Join Coding Droplets on Patreon. πŸ‘‰ https://www.patreon.com/CodingDroplets

Why Placement Matters More Than Implementation

All three extension points β€” middleware, action filters, and endpoint filters β€” let you intercept requests and responses. But they operate at fundamentally different layers of the ASP.NET Core pipeline, and that layer determines what context is available to you, what you can and cannot short-circuit, and how your concern composes with authentication, routing, model binding, and error handling.

Enterprise teams that treat all three as interchangeable end up with cross-cutting logic scattered across the pipeline without a principled reason for where things landed. When something goes wrong β€” a correlation ID that disappears mid-request, rate limiting that fires before authentication resolves the tenant, or an audit record that doesn't capture the validated model β€” the diagnostic path leads back to placement.

The architectural question is not "which one is easiest to write?" It is: "at which stage of the request lifecycle does this concern need to operate?"

The Pipeline Layer Model

ASP.NET Core processes requests through a sequential middleware pipeline. Middleware components execute before routing resolves an endpoint. Only after routing runs does the MVC/Minimal API layer become available, and with it, the filter pipeline.

Action filters (for controllers) and endpoint filters (for Minimal API endpoints) run inside the MVC/Minimal API execution layer β€” after model binding, after authentication and authorization middleware have had their say, and with full knowledge of the endpoint, controller, action, and bound model.

Think of it in three bands:

Band 1 β€” Pre-routing (Middleware only): The request is an HTTP message. You know the path, headers, and body stream. You do not know which endpoint will handle it. You have no model, no action context, no controller.

Band 2 β€” Post-routing, Pre-model binding (Middleware + Resource Filters): Routing has resolved the endpoint. You know what will handle the request, but the model has not been bound yet.

Band 3 β€” Post-model binding (Action Filters, Endpoint Filters, Exception Filters): The model is bound and validated. You have the full execution context β€” controller instance, action name, bound arguments, and in endpoint filters, the endpoint metadata.

When Middleware Is the Right Tool

Middleware belongs in Band 1. It is the right choice when the concern is genuinely infrastructure-level and applies regardless of which endpoint handles the request.

Correlation ID propagation is the clearest case. Every request that enters the system should carry a correlation ID β€” extracted from a header or generated fresh β€” before any application logic runs. This must happen before routing, before authentication, and before any filter has access to the request. Middleware is the only place where this is architecturally sound.

HTTPS redirection, HSTS enforcement, and static file serving are other canonical middleware concerns. They operate on the raw HTTP exchange and must resolve before the application pipeline engages.

Request logging and response time measurement also belong here when you need them to cover all requests, including requests that are rejected by authentication middleware or routed to static file handlers that never enter the MVC layer.

The important constraint: middleware has no knowledge of endpoints, controllers, actions, or bound models. If your concern needs any of that context, middleware is the wrong layer.

When Action Filters Are the Right Tool

Action filters belong in Band 3 β€” but specifically inside the MVC filter pipeline for controller-based APIs. They run after model binding, which makes them the correct location for concerns that need to inspect or act on the bound model.

Input validation enforcement is the canonical action filter case in enterprise APIs. You want to check ModelState.IsValid once, uniformly, across all controller actions, without repeating the check in every action method. An action filter that short-circuits with a 400 Bad Request when the model state is invalid is the right tool and the right layer. The bound model is available; the filter can surface exactly which fields failed validation.

Audit logging at the action level is another strong action filter use case. You need the controller name, action name, bound arguments, and the result of the action β€” all available in the action filter execution context. You cannot get this from middleware.

Action filters compose through filter inheritance on controllers and actions, which gives you fine-grained control. A filter applied to a specific controller affects only that controller's actions. This granularity is not available from middleware, which is always global.

The important constraint: action filters only run for requests processed by controllers. Requests that are rejected before routing, handled by static file middleware, or routed to Minimal API endpoints bypass the MVC filter pipeline entirely.

When Endpoint Filters Are the Right Tool

Endpoint filters are the Minimal API equivalent of action filters β€” but they carry some distinct characteristics that make them worth treating separately in enterprise decisions.

Like action filters, endpoint filters run post-model binding and have full access to endpoint metadata and arguments. But they operate through a different pipeline designed specifically for Minimal API endpoint delegates. They can inspect typed arguments directly.

In an enterprise codebase that has adopted Minimal APIs for internal services, endpoint filters are the correct location for endpoint-specific cross-cutting concerns: request argument validation, per-endpoint telemetry enrichment, tenant resolution from route parameters, and per-endpoint feature flag checks.

Endpoint filters also benefit from the endpoint metadata system. You can attach custom metadata to an endpoint and read that metadata inside the filter to vary behavior without writing separate filter implementations. This is a meaningful architectural advantage for enterprise APIs that need conditional cross-cutting behavior based on endpoint attributes.

The important constraint: endpoint filters are Minimal API–only. They do not apply to controller actions. If your application mixes controllers and Minimal API endpoints, you need a filter strategy for each surface.

The Enterprise Decision Matrix

Enterprise teams benefit from an explicit policy rather than ad-hoc placement decisions. The policy should cover five dimensions.

Scope: Does this concern apply to every HTTP request β€” including static files, health checks, and requests rejected before routing? Use middleware. Does it apply only to endpoint-handled requests? Use filters.

Context Required: Does the concern need endpoint metadata, controller identity, action name, or bound model arguments? Middleware cannot provide this. Filters can.

API Surface: Is this concern for controller-based APIs or Minimal API endpoints? Action filters for controllers, endpoint filters for Minimal APIs.

Granularity: Should the concern apply globally, per-controller, or per-action/endpoint? Middleware is always global. Action filters can be applied at any level of the controller hierarchy. Endpoint filters are applied per-endpoint or per-group.

Testability: Middleware requires an HttpContext and pipeline mock in unit tests. Action filters and endpoint filters can be unit-tested with minimal context stubs, which makes test setup lighter for concerns that don't need the full pipeline.

Composition and Ordering in Enterprise Systems

In practice, enterprise applications use all three layers β€” and the ordering between them is determined by the middleware pipeline registration sequence and filter ordering attributes.

A common enterprise pattern places authentication and rate limiting in middleware (Band 1), model validation and audit logging in action or endpoint filters (Band 3), and exception handling at both layers β€” a global middleware exception handler for infrastructure errors and an exception filter for application-domain errors that need to be translated into structured problem details.

This layered approach gives you defense in depth: infrastructure concerns are resolved early and cheaply, application concerns have full context when they need it, and error handling has the right vocabulary at each layer.

Teams that resist this separation often end up with middleware that tries to parse JSON bodies to make routing decisions, or filters that try to do things that require pre-routing knowledge β€” and neither works reliably at scale.

Cross-Cutting Concerns That Often Land in the Wrong Layer

Four concerns consistently end up misplaced in enterprise codebases.

Authentication enforcement is implemented in middleware and correctly so β€” but teams sometimes add secondary auth checks in action filters for convenience. This creates silent gaps. Middleware runs the check for all requests; the filter check only covers actions. Any Minimal API endpoint that bypasses the MVC filter pipeline will miss the filter-level check. The middleware check is the authoritative one.

Request size limiting sometimes appears in action filters, but it belongs in middleware. By the time an action filter runs, the request body has already been read and bound. Enforcing size limits at the filter level after the body has been buffered is wasteful. Middleware enforces the limit at read time.

Correlation ID propagation sometimes appears in action filters because teams want access to the controller context to decorate log scopes. The problem is that log records emitted by authentication middleware β€” which runs before any filter β€” will not carry the correlation ID. Propagation must be in middleware.

Response envelope wrapping is a common enterprise pattern β€” wrapping all API responses in a standard envelope. Teams sometimes implement this in action filters using ResultExecutingContext. This works for controller responses but silently skips Minimal API responses. If you have a mixed surface, you need an approach that covers both.

Governance Recommendations

For teams that want a durable policy rather than case-by-case decisions, three governance recommendations hold up across most enterprise ASP.NET Core systems.

First, document the placement policy in your architecture decision records and enforce it during code review. The rule is simple: if the concern is pre-routing and universal, it goes in middleware. If it is post-model-binding and endpoint-aware, it goes in a filter appropriate to the API surface.

Second, audit your existing middleware registrations and filter attributes periodically. In long-lived codebases, concerns drift across layers as features accumulate. An audit surfaces misplacements before they become incident contributors.

Third, integration test your pipeline composition, not just individual concerns. A test suite that exercises only isolated middleware or individual filters will not catch ordering bugs β€” the class of bug where authentication runs after rate limiting, or where correlation IDs are attached after the first log record is emitted.

FAQ

What is the main difference between middleware and action filters in ASP.NET Core? Middleware runs in the HTTP pipeline before routing resolves an endpoint and has no knowledge of controllers, actions, or bound models. Action filters run inside the MVC filter pipeline after routing and model binding, with full access to controller identity, action metadata, and bound model arguments.

Can I use middleware instead of action filters for model validation? No β€” not reliably. Middleware runs before model binding, so the model does not exist at that point. Model validation checks require a bound model and belong in an action filter or endpoint filter that runs post-binding.

Do endpoint filters replace action filters in ASP.NET Core Minimal APIs? Yes. Endpoint filters are the Minimal API counterpart to action filters. They serve the same architectural purpose β€” post-binding, endpoint-aware cross-cutting concerns β€” but they apply to Minimal API endpoint delegates rather than controller actions. Action filters do not apply to Minimal API endpoints.

Why shouldn't I put correlation ID propagation in an action filter? Because action filters run after authentication middleware, routing middleware, and other pipeline components have already executed and potentially emitted log records. If correlation ID propagation runs in a filter, any log records from preceding middleware stages will not carry the correlation ID. Propagation must happen in middleware, as early in the pipeline as practical.

How should enterprise teams handle cross-cutting concerns in APIs that mix controllers and Minimal API endpoints? For concerns that must be universal, implement them in middleware. For concerns that are endpoint-specific, use action filters for controller surfaces and endpoint filters for Minimal API surfaces β€” and document the dual implementation as an explicit architectural decision. Attempting to use a single filter mechanism for both surfaces will result in gaps.

Is it correct to use middleware for response envelope wrapping in enterprise APIs? For mixed-surface APIs (controllers + Minimal APIs), middleware is the safer location for response envelope wrapping because it covers all responses. Action filter–based wrapping silently skips Minimal API responses. The trade-off is that middleware-level wrapping requires inspecting and rewriting the response stream, which is more complex to implement correctly.

What ordering rules govern middleware and filters together? Middleware runs in registration order, before the MVC/Minimal API layer. Within the MVC layer, filters run in the following order: Authorization β†’ Resource β†’ Action/Exception (and for Minimal APIs: Endpoint filters in registration order). The two pipelines are sequential, not interleaved β€” middleware first, then filters, with no mechanism for a filter to run before middleware.

More from this blog

C

Coding Droplets

119 posts