Action Filtering in ASP.NET MVC

Introduction

In ASP.NET MVC, controllers define action methods that usually have a one-to-one relationship with possible user interactions, such as clicking a link or submitting a form. For example, when the user clicks a link, a request is routed to the designated controller and the corresponding action method is called.

However, sometimes you want to perform logic either before an action method is called or after an action method runs. To support this, ASP.NET MVC enables you to create action filters. Action filters are custom attributes that provide a declarative means to add pre-action and post-action behavior to controller action methods.

The possible uses for action filters are as varied as the actions to which they can be applied. Some possible uses for action filters include the following:

  • Logging – tracking user interactions.

  • Authentication and authorization – restricting user access.

  • Output caching – saving the results of an action.

  • Web crawler filtering – changing behavior based on the browser user agent.

  • Localization – setting the locale.

  • Dynamic actions – injecting an action into a controller.

Implementing an Action Filter

An action filter is implemented as an attribute class that inherits from ActionFilterAttribute. ActionFilterAttribute is an abstract class that has two virtual methods that you can override, OnActionExecuting and OnActionExecuted.

The ASP.NET VMC framework will call the OnActionExecuting method of your action filter before it calls any action method that is marked with your action filter attribute. Similarly, the framework will call the OnActionExecuted method after the action method has finished. Note that you do not have to implement both methods when you create an action filter.

The following example shows a simple action filter that logs trace messages before and after an action method is called.

[C#]
public class LoggingFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(FilterExecutingContext 
        filterContext)
    {
        filterContext.HttpContext.Trace.Write("Starting: " + 
        filterContext.ActionMethod.Name);
    }

    public override void OnActionExecuted(FilterExecutedContext 
        filterContext)
    {
        if (filterContext.Exception != null)
        {
            filterContext.HttpContext.Trace.Write("Exception thrown");
        }
    }
}

Action Filter Context

The OnActionExecuting method has a single parameter of type FilterExecutingContext, and the OnActionExecuted method has a corresponding parameter of type FilterExecutedContext. Both context classes inherit from the FilterContext class. FilterContext inherits from ControllerContext and includes an ActionMethod property. You can use the ActionMethod property to identify which action the filter is currently applied to.

The FilterExecutingContext class contains a Cancel property that enables you to cancel the action.

The FilterExecutedContent class contains an Exception property and an ExceptionHandled property. If the Exception property is null, there is no exception in the action stack, which indicates that no error occurred when the action method ran. If the Exception property is not null and the filter knows how to handle the exception, the filter can handle the exception and then signal that it has done so by setting the ExceptionHandled property to true. Even if the ExceptionHandled property is true, the OnActionExecuted method of any additional action filters in the stack will still be called with the exception information. This enables scenarios such as letting a logging filter log an exception even though the exception has been handled. In general, an action filter should not handle an exception unless the error is specific to that filter.

Marking an Action Method with a Filter Attribute

You can apply an action filter to as many action methods as you want. The following example shows a controller that contains action methods that are marked with an action-filter attribute. In this case, all the action methods in the controller will invoke the action filter.

public class HomeController : Controller
{
    [LoggingFilter]
    public void Index()
    {
        RenderView("Index");
    }

    [LoggingFilter]
    public void About()
    {
        RenderView("About");
    }

    [LoggingFilter]
    public void ClickMe()
    {
        HttpContext.Trace.Write("Button was clicked.");
        InvokeAction("Index");
    }
}

Implementing a Controller-Scoped Action Filter

The ASP.NET MVC Controller class defines OnActionExecuting and OnActionExecuted methods that you can override. When you override one or both of these methods, you effectively define an action filter that will be applied to all action methods of that controller. Technically, these methods do not constitute an action filter; however, the functionality they provide is similar.

In the following example, the controller-level OnActionExecuting and OnActionExecuted methods apply to all action methods in the controller.

public class HomeController : Controller
{
    public void Index()
    {
        RenderView("Index");
    }

    public void About()
    {
        RenderView("About");
    }

    public void ClickMe()
    {
        HttpContext.Trace.Write("Button was clicked.");
        InvokeAction("Index");
    }

    protected override void OnActionExecuting(FilterExecutingContext 
        filterContext)
    {
        filterContext.HttpContext.Trace.Write("Starting: " + 
            filterContext.ActionMethod.Name);
    }

    protected override void OnActionExecuted(FilterExecutedContext 
        filterContext)
    {
        if (filterContext.Exception != null)
        {
            filterContext.HttpContext.Trace.Write("Exception thrown");
        }
    }
}

Scope of Action Filters

In addition to marking individual action methods with an action filter, you can mark a controller class as a whole with an action filter. In that case, the filter applies to all action methods of that controller.

Additionally, if your controller derives from another controller, the base controller might have its own action-filter attributes. Likewise, if your controller overrides an action method from the base controller, the method might have its own action-filter attributes and those it inherits from the overridden action method.

To make it easier to understand how action filters work together, action methods are grouped into scopes. A scope defines where the attribute applies, such as whether it marks a class or a method, and whether it marks a base class or a derived class.

Order of Execution for Action Filters

Each action filter has an Order property, which is used to determine the order that filters are executed within the scope of the filter. The Order property takes an integer value that must be 0 (default) or higher (with one exception). Omitting the Order property give the filter an order value of -1, which indicates an unspecified order. Any action filters within a scope whose Order property is set to -1 will be executed in a nondeterministic order, but before the filters that have a specified order.

When an Order property of a filter is specified, it must be set to a unique value within a scope. If two or more action filters within a scope have the same Order property value, an exception is thrown.

The following example shows how the Order property affects the order in which action filters are executed.

[C#]
[Filter1(Order = 2)]
[Filter2(Order = 3)]
[Filter3(Order = 1)]
public void Index()
{
    RenderView("Index");
}

In the previous example, the action filters would be executed in the following order: Filter3, Filter1, and then Filter2.

Within a scope, action filter methods are typically executed in the following order:

  1. OnActionExecuting virtual method of the controller.

  2. OnActionExecuting method of any filters that are applied to the current controller, in this order:

    1. Base class

    2. Any derived class

  3. OnActionExecuting method of filters that are applied to the action method, in this order:

    1. Base class

    2. Derived class

  4. Action method

  5. OnActionExecuted method of filters that are applied to the action method, in this order:

    1. Derived class

    2. Base class

  6. OnActionExecuted method of filters that are applied to the current controller, in this order:

    1. Derived class

    2. Base class

  7. OnActionExecuted virtual method of the controller.

Example of Action Filter Order of Execution

The following example shows an MVC application that contains two action filters. The DebugFilter filter writes trace messages, and the ThrowExceptionFilter filter causes an exception. The application also contains a base controller, a derived controller, and a view.

The following class defines the DebugFilter filter:

[C#]
public class DebugFilterAttribute : ActionFilterAttribute
{
    public static void Reset()
    {
        counter = 1;
        indent = 0;
        incrementIndex = true;
    }

    public static int Counter
    {
        get
        {
            return counter++;
        }
    }
    
    static int counter
    {
        get
        {
            return (int)(HttpContext.Current.Items["counter"] ?? 1);
        }
        set
        {
            HttpContext.Current.Items["counter"] = value;
        }
    }

    static int indent
    {
        get
        {
            return (int)(HttpContext.Current.Items["indent"] ?? 1);
        }
        set
        {
            HttpContext.Current.Items["indent"] = value;
        }
    }

    static bool incrementIndex
    {
        get
        {
            return (bool)(HttpContext.Current.Items["incrementIndex"] ?? true);
        }
        set
        {
            HttpContext.Current.Items["incrementIndex"] = value;
        }
    }

    public string Message { get; set; }

    public int Id { get; set; }

    public override void OnActionExecuting(FilterExecutingContext 
         filterContext)
    {
        HttpContext.Current.Trace.Write(
            "Action Filter", 
            string.Format("{0}: {3}(PRE) DebugFilter.OnActionExecuting"
            + " - Order={1} Message='{2}'", 
            Counter, 
            this.Order, 
            this.Message, 
            Indent));
    }

    public static string Indent
    {
        get
        {
            indent += (incrementIndex ? 1 : -1);
            string indentText = string.Empty;
            for (int i = 0; i < indent; i++)
                indentText += " ";
            return indentText;
        }
    }

    public static void StartDecrement()
    {
        incrementIndex = false;
    }
}

The following example shows an action filter class that throws an exception when it is invoked.

[C#]
public class ThrowExceptionFilter : DebugFilterAttribute
{
    public override void 
        OnActionExecuting(
            System.Web.Mvc.FilterExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        DebugFilterAttribute.StartDecrement();
        throw new InvalidOperationException(
            "Exception thrown by the filter");
    }
}

The following example shows a class that defines the base controller with action filters applied.

[C#]
[DebugFilter(Message = "(CONTROLLER) MyBaseController", Order = 1)]
[DebugFilter(Message = "(CONTROLLER) MyBaseController", Order=2)]
public class MyBaseController : Controller
{
    [ControllerAction]
    [DebugFilter(Message = "(ACTION) MyBaseController.Index()", Order=2)]
    [DebugFilter(Message = "(ACTION) MyBaseController.Index()", Order=1)]
    public virtual void Index()
    {
        RenderView("Index");
    }
}

The following example shows a class that defines the derived controller with action filters applied. Notice that this controller implements its OnActionExecuting and OnActionExecuted methods.

[C#]
[DebugFilter(Message = "(CONTROLLER) MyDerivedController", Order = 2)]
[DebugFilter(Message = "(CONTROLLER) MyDerivedController", Order=1)]
public class MyDerivedController : MyBaseController
{
    [ControllerAction]
    [DebugFilter(Message = "(ACTION) MyDerivedController.Index()", Order=1)]
    [DebugFilter(Message = "(ACTION) MyDerivedController.Index()", Order = 2)]
    public override void Index()
    {
        base.Index();
        HttpContext.Trace.Write(
            "Action Filter", 
            string.Format("{0}: {1}(ACTION) "
            + "MyDerivedController.Index()", 
            DebugFilterAttribute.Counter, 
            DebugFilterAttribute.Indent));
        DebugFilterAttribute.StartDecrement();
    }

    protected override void OnActionExecuting(FilterExecutingContext filterContext)
    {
        //The Beginning
        DebugFilterAttribute.Reset();

        HttpContext.Trace.Write(
            "Action Filter", 
            string.Format("{0}: {1}(PRE) "
            + "MyDerviedController.OnActionExecuting VIRTUAL METHOD",
            DebugFilterAttribute.Counter, 
            DebugFilterAttribute.Indent));
    }

The following example shows the Index view for the application whose Trace property is set to true.

<%@ Page Language="C#" CodeBehind="Index.aspx.cs" Inherits="ActionFilterTests.Views.MyDerived.Index" Trace="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Attribute Filter Test</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        Hello World
    </div>
    </form>
</body>
</html>

The following example shows the output trace that is displayed when this application runs.

Action Filter 1:  (PRE) MyDerviedController.OnActionExecuting VIRTUAL METHOD   
Action Filter 2:   (PRE) DebugFilter.OnActionExecuting - Order=1 Message='(CONTROLLER) MyBaseController
Action Filter 3:    (PRE) DebugFilter.OnActionExecuting - Order=2 Message='(CONTROLLER) MyBaseController
Action Filter 4:     (PRE) DebugFilter.OnActionExecuting - Order=1 Message='(CONTROLLER) MyDerivedController
Action Filter 5:      (PRE) DebugFilter.OnActionExecuting - Order=2 Message='(CONTROLLER) MyDerivedController
Action Filter 6:       (PRE) DebugFilter.OnActionExecuting - Order=1 Message='(ACTION) MyDerivedController.Index()
Action Filter 7:        (PRE) DebugFilter.OnActionExecuting - Order=2 Message='(ACTION) MyDerivedController.Index()
Action Filter 8:         (ACTION) MyDerivedController.Index()

This topic is ASP.NET 3.5 Extensions pre-release documentation and is unsupported by Microsoft. Blank topics are included as placeholders and existing content is subject to change in future releases. To provide feedback or ask questions about the release, please go to the ASP.NET 3.5 Extensions forums.