Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Understanding ASP.NET Web API Filters and Their Execution Order

Notes 1

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:

  1. Within each scope, Authorization filters execute before Action filters
  2. For Authorization and OnActionExecuting methods, execution proceeds from Global → Controller → Action
  3. For OnActionExecuted, the order reverses: Action → Controller → Global
  4. Exception filters follow the Action → Controller → Global pattern
  5. 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.

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.