Partager via


Creating Custom Action Filters

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 in order to track user interactions.

  • "Anti-image-leeching" to prevent images from being loaded in pages that are not on your site.

  • Web crawler filtering to change application behavior based on the browser user agent.

  • Localization to set the locale.

  • Dynamic actions to inject an action into a controller.

Implementing a Custom Action Filter

An action filter is implemented as an attribute class that inherits from ActionFilterAttribute. ActionFilterAttribute is an abstract class that has four virtual methods that you can override: OnActionExecuting, OnActionExecuted, OnResultExecuting, and OnResultExecuted. To implement an action filter, you must override at least one of these methods.

The ASP.NET MVC 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.

The OnResultExecuting method is called just before the ActionResult instance that is returned by your action is invoked. The OnResultExecuted method is called just after the result is executed. These methods are useful for performing actions such as logging, output caching, and so forth.

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

Public Class LoggingFilterAttribute
    Inherits ActionFilterAttribute

    Public Overrides Sub OnActionExecuting(ByVal filterContext As ActionExecutingContext)
        filterContext.HttpContext.Trace.Write("(Logging Filter)Action Executing: " + _
                filterContext.ActionDescriptor.ActionName)

        MyBase.OnActionExecuting(filterContext)
    End Sub

    Public Overrides Sub OnActionExecuted(ByVal filterContext As ActionExecutedContext)
        If Not filterContext.Exception Is Nothing Then
            filterContext.HttpContext.Trace.Write("(Logging Filter)Exception thrown")
        End If

        MyBase.OnActionExecuted(filterContext)
    End Sub
End Class
public class LoggingFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.HttpContext.Trace.Write("(Logging Filter)Action Executing: " +
            filterContext.ActionDescriptor.ActionName);

        base.OnActionExecuting(filterContext);
    }

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

        base.OnActionExecuted(filterContext);
    }
}

Action Filter Context

Each of the controller event handlers for action filtering takes a context object as a parameter. The following list shows the filter event handlers and the context type that each takes.

All context classes inherit from the ControllerContext class and include an ActionDescriptor property. You can use the ActionDescriptor property to identify which action the filter is currently applied to.

The ActionExecutingContext and ResultExecutingContext classes contain a Cancel property that enables you to cancel the action.

The ActionExecutedContent and ResultExecutedContext classes contain an Exception property and an ExceptionHandled property. If the Exception property is null, it 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 or OnResultExecuted method of any additional action filters in the stack will be called and exception information will be passed to them. 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 need. 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 same action filter.

<HandleError()> _
Public Class HomeController
    Inherits System.Web.Mvc.Controller

    <LoggingFilter()> _
    Function Index() As ActionResult
        ViewData("Message") = "Welcome to ASP.NET MVC!"

        If TempData.ContainsKey("Clicked") Then
            ViewData("ClickMessage") = "You clicked this button."
            ViewData("Clicked") = True
        Else
            ViewData("Clicked") = False
        End If

        Return View()
    End Function

    <LoggingFilter()> _
    Function About() As ActionResult
        Return View()
    End Function

    <LoggingFilter()> _
    Public Function ClickMe() As ActionResult
        TempData("Clicked") = True

        Return RedirectToAction("Index")
    End Function
End Class
[HandleError]
public class HomeController : Controller
{
    [LoggingFilter]
    public ActionResult Index()
    {
        ViewData["Message"] = "Welcome to ASP.NET MVC!";

        if (TempData.ContainsKey("Clicked"))
        {
            ViewData["Clicked"] = true;
        }
        else ViewData["Clicked"] = false;

        if ((Boolean)ViewData["Clicked"] == true)
        {
            ViewData["ClickMessage"] = "You clicked this button.";
            ViewData["Clicked"] = false;
        }

        return View();
    }

    [LoggingFilter]
    public ActionResult About()
    {
        return View();
    }

    [LoggingFilter]
    public ActionResult ClickMe()
    {
        TempData["Clicked"] = true;

        return RedirectToAction("Index");
    }
}

Executing Code Before and After an Action from Within a Controller

The ASP.NET MVC Controller class defines OnActionExecuting and OnActionExecuted methods that you can override. When you override one or both of these methods, your logic will execute before or after all action methods of that controller. This functionality is like action filters, but the methods are controller-scoped.

The following example shows controller-level OnActionExecuting and OnActionExecuted methods that apply to all action methods in the controller.

<HandleError()> _
Public Class HomeController
    Inherits System.Web.Mvc.Controller

    Function Index() As ActionResult
        ViewData("Message") = "Welcome to ASP.NET MVC!"

        If TempData.ContainsKey("Clicked") Then
            ViewData("ClickMessage") = "You clicked this button."
            ViewData("Clicked") = True
        Else
            ViewData("Clicked") = False
        End If

        Return View()
    End Function

    Function About() As ActionResult
        Return View()
    End Function

    Public Function ClickMe() As ActionResult
        TempData("Clicked") = True

        Return RedirectToAction("Index")
    End Function

    <NonAction()> _
    Protected Overrides Sub OnActionExecuting(ByVal filterContext As ActionExecutingContext)
        filterContext.HttpContext.Trace.Write("(Controller)Action Executing: " + _
                filterContext.ActionDescriptor.ActionName)

        MyBase.OnActionExecuting(filterContext)
    End Sub

    <NonAction()> _
    Protected Overrides Sub OnActionExecuted(ByVal filterContext As ActionExecutedContext)
        If Not filterContext.Exception Is Nothing Then
            filterContext.HttpContext.Trace.Write("(Controller)Exception thrown")
        End If

        MyBase.OnActionExecuted(filterContext)
    End Sub
End Class
[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewData["Message"] = "Welcome to ASP.NET MVC!";

        if (TempData.ContainsKey("Clicked"))
        {
            ViewData["Clicked"] = true;
        }
        else ViewData["Clicked"] = false;

        if ((Boolean)ViewData["Clicked"] == true)
        {
            ViewData["ClickMessage"] = "You clicked this button.";
            ViewData["Clicked"] = false;
        }

        return View();
    }

    public ActionResult About()
    {
        ViewData["Title"] = "About Page";

        return View();
    }

     public ActionResult ClickMe()
    {
        TempData["Clicked"] = true;

        return RedirectToAction("Index");
    }

    [NonAction]
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.HttpContext.Trace.Write("(Controller)Action Executing: " +
            filterContext.ActionDescriptor.ActionName);

        base.OnActionExecuting(filterContext);
    }

    [NonAction]
    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Exception != null)
            filterContext.HttpContext.Trace.Write("(Controller)Exception thrown");

        base.OnActionExecuted(filterContext);
    }
}

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 a 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 in the scope of the filter. The Order property takes an integer value that must be 0 (default) or greater (with one exception). Omitting the Order property gives the filter an order value of -1, which indicates an unspecified order. Any action filters in a scope whose Order property is set to -1 will be executed in an undetermined 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 in a scope. If two or more action filters in a scope have the same Order property value, an exception is thrown.

Action filters are executed in an order that meets the following constraints. If more than one ordering fulfills all these constraints, ordering is undetermined.

  1. The implementation of the OnActionExecuting and OnActionExecuted methods on the controller always go first. For more information, see Executing Code Before and After an Action from Within a Controller.

  2. Unless the Order property is set explicitly, an action filter has an implied order of -1.

  3. If the Order property of multiple action filters are explicitly set, the filter with the lowest value executes before those with greater values, as shown in the following example:

    <Filter1(Order = 2)> _ 
    <Filter2(Order = 3)> _ 
    <Filter3(Order = 1)> _ 
    Public Sub Index()
        View("Index")
    End Sub
    
    [Filter1(Order = 2)]
    [Filter2(Order = 3)]
    [Filter3(Order = 1)]
    public void Index()
    {
        View("Index");
    }
    

    In this example, action filters would execute in the following order: Filter3, Filter1, and then Filter2.

  4. If two action filters have the same Order property value, and if one action filter is defined on a type and the other action filter is defined on a method, the action filter defined on the type executes first. The following example shows two an action filter defined on the type and another defined on a method.

    <FilterType(Order = 1)> _ 
    Public Class MyController
        <FilterMethod(Order = 1)> _ 
        Public  Sub Index()
            View("Index")
        End Sub
    End Class
    
    [FilterType(Order = 1)]
    public class MyController
    {
        [FilterMethod(Order = 1)]
        public void Index()
        {
            View("Index");
        }
    }
    

    In this example, FilterType executes before FilterMethod.

See Also

Tasks

How to: Create a Custom Action Filter

Other Resources

Action Filtering in ASP.NET MVC Applications