Understanding ASP.NET Web API Filters and Their Execution Order
Understanding ASP.NET Web API Filters and Their Execution Order
Web API embraces Aspect-Oriented Programming (AOP) concepts, allowing developers to intercept requests at specific pipeline stages using Filters. This architectural pattern promotes the DRY (Don't Repeat Yourself) principle by enabling centralized handling of cross-cutting concerns such as authentication, parameter encryption, and input validation. This article covers Filter implementation, registration methods, and execution order.
Filter Types and Implementation
The framework provides three built-in Filter types:
| Filter Type | Interface | Purpose |
|---|---|---|
| Authorization | IAuthorizationFilter | Executes first; handles request authentication |
| Action | IActionFilter | Runs before and after Action execution |
| Exception | IExceptionFilter | Executes when unhandled exceptions occur |
Authorization Filter
A basic authentication Filter implementation:
public class TokenAuthAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext context)
{
var anonymousAttr = context.ActionDescriptor
.GetCustomAttributes<AllowAnonymousAttribute>().FirstOrDefault();
if (anonymousAttr != null)
{
return;
}
var authHeader = context.Request.Headers.Authorization;
bool isValid = authHeader != null && authHeader.Parameter == "SECRET_KEY_123";
if (!isValid)
{
context.Response = context.Request.CreateErrorResponse(
HttpStatusCode.Unauthorized,
new HttpError("Invalid token"));
}
}
}
This Filter checks for the presence of an Authorization header containing a specific tokeen value. Failed authentication returns a 401 status code with an error message.
Registering Filters
Filters can be registered at three different scopes:
Action-level registration:
public class UserController : ApiController
{
[TokenAuth]
public CreateResult Post(CreateUserRequest request)
{
return new CreateResult { Id = "USER_001" };
}
}
Controller-level registration:
[TokenAuth]
public class UserController : ApiController
{
public CreateResult Post(CreateUserRequest request)
{
return new CreateResult { Id = "USER_001" };
}
}
Global registration:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Filters.Add(new TokenAuthAttribute());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
For endpoints that should bypass authentication (such as login), use the AllowAnonymousAttribute:
[AllowAnonymous]
public CreateResult PostLogin(LoginRequest request)
{
return new CreateResult { Id = "SESSION_TOKEN" };
}
Action Filter
ActionFilterAttribute provides two interception points: OnActionExecuting and OnActionExecuted, each with synchronous and asynchronous variants.
OnActionExecuting runs before the Action method executes, making it ideal for model validation:
public class ValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext context)
{
if (!context.ModelState.IsValid)
{
context.Response = context.Request.CreateErrorResponse(
HttpStatusCode.BadRequest,
context.ModelState);
}
}
}
This Filter validates model state and returns a 400 Bad Request with detailed error information when validation fails. The accompanying model definition:
public class LoginRequest
{
[Required(ErrorMessage = "Username is required")]
public string UserName { get; set; }
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; }
}
OnActionExecuted runs after the Action completes, suitable for response modification:
public override async Task OnActionExecutedAsync(
HttpActionExecutedContext context,
CancellationToken cancellationToken)
{
var cipherKey = 42;
var rawData = await context.Response.Content.ReadAsByteArrayAsync();
for (int i = 0; i < rawData.Length; i++)
{
rawData[i] = (byte)(rawData[i] ^ cipherKey);
}
context.Response.Content = new ByteArrayContent(rawData);
context.Response.Content.Headers.ContentType =
MediaTypeHeaderValue.Parse("application/octet-stream");
}
This example applies a simple XOR cipher to response payloads for basic encryption. More robust implementations might use AES, DES, or RSA algorithms.
Exception Filter
Exception filters handle uncaught exceptions, preventing users from seeing stack traces or cryptic error pages:
public class GlobalExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is BusinessRuleException businessError)
{
context.Response = context.Request.CreateResponse(
HttpStatusCode.BadRequest,
new { Error = businessError.Message });
}
else
{
context.Response = context.Request.CreateResponse(
HttpStatusCode.InternalServerError,
new { Error = "An unexpected error occurred" });
}
}
}
This filter categorizes exceptions into expected business errors (returning 400) and unexpected system errors (returning 500). A controller demonstrating this behavier:
[GlobalExceptionFilter]
public class CalculatorController : ApiController
{
public int Divide(int dividend, int divisor)
{
if (dividend < divisor)
{
throw new BusinessRuleException("Dividend must exceed divisor");
}
if (divisor == 0)
{
throw new InvalidOperationException("Division by zero");
}
return dividend / divisor;
}
}
Exception filters only capture exceptions that occur after controller initialization completes. Exceptions thrown during constructor execution bypass this mechanism entirely.
Filter Execution Order
Web API uses a different execution model than MVC and does not support the Order property for controlling execution sequence. Filters operate within three scopes: Global, Controller, and Action.
The execution rules are:
- Within each scope, Authorization filters execute before Action filters
- For Authorization and OnActionExecuting methods, execution proceeds from Global → Controller → Action
- For OnActionExecuted, the order reverses: Action → Controller → Global
- Exception filters follow the Action → Controller → Global pattern
- If a filter sets the Response property, subsequent filters in the chain are skipped
This hierarchical execution model ensures that authentication decisions are made before business logic runs, and that exception handling consistently processes errors regardless of where they originate.