.NET Core / Web API71 min total · 19 parts
Building REST APIs with ASP.NET Core: Routing, Middleware, and Dependency Injection
Part 15 of 19 · ~2 min
Filters: Cross-Cutting Logic Around Actions
A filter hooks into specific moments around an action running, and it's the tool to reach for over plain middleware once the job genuinely needs MVC context — the thing a filter can see that raw middleware structurally cannot is the action's own arguments and whatever it eventually returns, not just the bare request and response. Bench's ReservationAuditFilter is what actually let Dana reconstruct the Tuesday-afternoon timeline after the fact:
public class ReservationAuditFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var kioskId = context.HttpContext.Request.Headers["X-Kiosk-Id"];
logger.LogInformation("Reservation action starting: {Action} from kiosk {Kiosk}",
context.ActionDescriptor.DisplayName, kioskId);
}
public void OnActionExecuted(ActionExecutedContext context)
{
logger.LogInformation("Reservation action finished: {Action}, result: {Result}",
context.ActionDescriptor.DisplayName, context.Result?.GetType().Name);
}
}
[ServiceFilter(typeof(ReservationAuditFilter))]
[HttpPost]
public async Task<ActionResult<Reservation>> Create(int toolId, CreateReservationRequest request) { /* ... */ }
Without that log line naming which kiosk fired which request at which second, the postmortem would have had a lot more guesswork in it — it's the detail that let Dana line up Marcus's and Yuki's requests to within the same second and confirm they'd hit two different kiosks, which is what pointed the investigation at concurrency in the first place rather than, say, a caching bug in the SPA.
| Filter type | Where it sits |
|---|---|
| Authorization filters | First in line, deciding the gatekeeping question up front: does this request get to go any further |
| Resource filters | Sandwich model binding — able to short-circuit the whole thing before binding is ever attempted |
| Action filters | Right before the action runs and right after it returns — where ReservationAuditFilter lives |
| Exception filters | Only triggered when an action throws and nothing inside it caught it |
| Result filters | Wrap the moment the action's return value actually gets serialized onto the response |
There's an obvious place these could step on each other — exception filters versus the middleware-based handler from the previous chapter — and Bench draws the line by scope: middleware owns anything that's genuinely app-wide, an exception filter only earns its keep when the handling logic actually needs to see action or controller context. So far, nothing in Bench has needed that second thing badly enough to justify one.