Introduction

Action filters are an integral part of many modern web frameworks, including ASP.NET MVC, ASP.NET Core, and others. They allow you to intercept and modify the request and response pipeline for an application's actions or methods. Action filters provide a way to apply cross-cutting concerns, such as authentication, logging, validation, caching, and more, to multiple actions or controllers without duplicating code.

How do action filters work, and their primary purposes?

In the context of ASP.NET MVC or ASP.NET Core, action filters are implemented by creating classes that derive from specific filter attributes or implementing certain interfaces. Commonly used action filters include:

Here's a more comprehensive example using ASP.NET Core that demonstrates the use of various types of action filters: authorization, action, result, exception, and resource filters. This example showcases how you can intercept and modify the request/response pipeline to apply cross-cutting concerns.

Let's create an example application with a simple "Task Manager" feature. We'll implement authentication using an authorization filter, log actions using an action filter, handle exceptions using an exception filter, and demonstrate resource filters as well.

Authorization Filter (AuthFilter.cs)

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;

Author: Sardar Mudassar Ali Khan
public class AuthFilter : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        // Simulate authentication logic
        bool isAuthenticated = true; // In a real scenario, this would be based on user authentication

        if (!isAuthenticated)
        {
            context.Result = new UnauthorizedResult();
        }
    }
}

Action Filter (LogFilter.cs)

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;

Author: Sardar Mudassar Ali Khan
public class LogFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine("Before action execution: Logging request details");
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine("After action execution: Logging response details");
    }
}

Result Filter (CacheFilter.cs)

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;

Author: Sardar Mudassar Ali Khan
public class CacheFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Check cache for the result
        var cachedResult = /* Check cache logic */;
        if (cachedResult != null)
        {
            context.Result = cachedResult;
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Cache the result
        /* Cache logic */
    }
}

Exception Filter (ExceptionFilter.cs)

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;

Author: Sardar Mudassar Ali Khan
public class ExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        Console.WriteLine($"Exception occurred: {context.Exception.Message}");
        context.Result = new ObjectResult("An error occurred") { StatusCode = 500 };
        context.ExceptionHandled = true;
    }
}

Resource Filter (ResourceFilter.cs)

using Microsoft.AspNetCore.Mvc.Filters;
using System;

Author: Sardar Mudassar Ali Khan
public class ResourceFilter : IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        Console.WriteLine("Resource is being executed: ResourceFilter.OnResourceExecuting");
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
        Console.WriteLine("Resource execution is complete: ResourceFilter.OnResourceExecuted");
    }
}

Controller (TaskController.cs)

using Microsoft.AspNetCore.Mvc;

Author: Sardar Mudassar Ali Khan
[AuthFilter] // Apply authorization filter to the whole controller
public class TaskController : Controller
{
    [LogFilter] // Apply action filter to this action
    public IActionResult Index()
    {
        Console.WriteLine("Executing Index action");
        return View();
    }

    [CacheFilter] // Apply result filter to this action
    public IActionResult GetTasks()
    {
        Console.WriteLine("Executing GetTasks action");
        return View();
    }

    [ResourceFilter] // Apply resource filter to this action
    [ExceptionFilter] // Apply exception filter to this action
    public IActionResult DeleteTask(int id)
    {
        Console.WriteLine("Executing DeleteTask action");
        // Simulate an exception
        throw new Exception("Failed to delete task.");
    }
}

Conclusion

Leveraging action filters to intercept and modify the request/response pipeline offers a robust approach for applying cross-cutting concerns in web applications. These filters empower developers to encapsulate and manage aspects such as authentication, logging, validation, and more while maintaining a modular and reusable codebase. By strategically inserting action filters at various points within the application's execution flow, developers can ensure consistent behavior across different actions and controllers. This approach not only enhances the application's security and performance but also fosters efficient code management, making it easier to maintain and scale the application over time.