Web API 2 Using ActionFilterAttribute, OverrideActionFiltersAttribute and IoC Injection

ActionFilters are a great way to add extra functionality to your Web API service. This article shows examples of how the ActionFilters work together, how the filters can be overrided and how the filters can be used together with an IoC. The diagram underneath shows how the filters are called in the Web API lifecycle.

Important! Filters for Web API are not the same as filters for MVC. The Web API filters are found in the System.Web.Http.Filters namespace.

Taken from Microsoft MSDN
WebApiFilters01

Code: https://github.com/damienbod/WebApi2AttributeFilters

ActionFilterAttribute

To demonstate the different features of action filters, different custom ActionFilters have been defined. Each filter writes to the output window.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Web;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace WebApi2Attributes.Attributes
{
    public class Action1DebugActionWebApiFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            // pre-processing
            Debug.WriteLine("ACTION 1 DEBUG pre-processing logging");
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var objectContent = actionExecutedContext.Response.Content as ObjectContent;
            if (objectContent != null)
            {
                var type = objectContent.ObjectType; //type of the returned object
                var value = objectContent.Value; //holding the returned value
            }

            Debug.WriteLine("ACTION 1 DEBUG  OnActionExecuted Response " + actionExecutedContext.Response.StatusCode.ToString());
        }
    }
}

ActionFilters Example 1:
This example demonstrates how action filters can be used for all controller methods and for action methods. The code is as follows:

    [Class1DebugActionWebApiFilter]
    [Class2DebugActionWebApiFilter]
    [RoutePrefix("api/MyExamples")] 
    public class ValuesController : ApiController
    {
        /// <summary>
        /// Basic filters class and then action
        /// </summary>
        /// <returns></returns>
        [Route("test1")]
        [AcceptVerbs("GET")]
        [Action1DebugActionWebApiFilter]
        public IEnumerable<string> UsesBothAttributes()
        {
            return new string[] { "value1", "value2" };
        }
    }

When the test1 is requested in a browser HTTP GET /api/MyExamples/test1, the following results can be viewed in the output window:
ActionFilters01

The controller ActionFilters are wrapped around the action method ActionFilters.

ActionFilters Example 2:
This example shows how the framework attribute OverrideActionFiltersAttribute can be used to disable the controller level ActionFilter attributes. This attribute disables all ActionFilters defined at the controller level, but none from the action method.

It is not possible to disable a single attribute type!

    [Class1DebugActionWebApiFilter]
    [Class2DebugActionWebApiFilter]
    [RoutePrefix("api/MyExamples")] 
    public class ValuesController : ApiController
    {
        /// <summary>
        /// Framework OverrideActionFiltersAttribute turns off class filters of type IActionFilter
        /// </summary>
        /// <returns></returns>
        [Route("test2")]
        [AcceptVerbs("GET")]
        [Action1DebugActionWebApiFilter]
        [OverrideActionFiltersAttribute]
        public IEnumerable<string> UsesJustActionDebugAttribute()
        {
            return new string[] { "value1", "value2" };
        }
    }

As the results show, no controller level attributes are called. The FilterGroup class defines the override logic.

ActionFilters02

ActionFilters Example 3:
This example is the same as above except a custom override is used. Like before, all controller level attributes are disabled.

    [Class1DebugActionWebApiFilter]
    [Class2DebugActionWebApiFilter]
    [RoutePrefix("api/MyExamples")] 
    public class ValuesController : ApiController
    {
        /// <summary>
        /// MyOverrideDebugActionWebApiFilter turns off class filters of type IActionFilter
        /// </summary>
        /// <returns></returns>
        [Route("test3")]
        [AcceptVerbs("GET")]
        [Action1DebugActionWebApiFilter]
        [MyOverrideDebugActionWebApiFilter]
        [Action2DebugActionWebApiFilter]
        public IEnumerable<string> UseActionOverrideActionDebugAttribute()
        {
            return new string[] { "value1", "value2" };
        }
    }

And the custom override:

using System;
using System.Web.Http.Filters;

namespace WebApi2Attributes.Attributes
{
    public class MyOverrideDebugActionWebApiFilter : ActionFilterAttribute, IOverrideFilter
    {
        public Type FiltersToOverride
        {
            get
            {
                return typeof(IActionFilter);
            }
        }
    }
}

Note: This attribute only works if the IActionFilter is returned in the FiltersToOverride property. You cannot override your custom action directly. Only groups of filters and in this example, all filters from group IActionFilter are overrided.

ActionFilters03

ActionFilters Example 4:
This example is the same as example 1, except the action method has 4 different ActionFilters defined.

    [Class1DebugActionWebApiFilter]
    [Class2DebugActionWebApiFilter]
    [RoutePrefix("api/MyExamples")] 
    public class ValuesController : ApiController
    {
        /// <summary>
        /// Class filters are wrapped around action filters
        /// </summary>
        /// <returns></returns>
        [Route("test4")]
        [AcceptVerbs("GET")]
        [Action1DebugActionWebApiFilter]
        [Action2DebugActionWebApiFilter]
        [Action3DebugActionWebApiFilter]
        [Action4DebugActionWebApiFilter]
        public IEnumerable<string> Use4ActionsDebugAttribute()
        {
            return new string[] { "value1", "value2" };
        }
    }

ActionFilters04

As shown in the results, the order of the ActionFilters on the action method is NOT sorted!

ActionFilters Example 5:
This example shows how an ActionFilter can be used to change the request data.

    [Class1DebugActionWebApiFilter]
    [Class2DebugActionWebApiFilter]
    [RoutePrefix("api/MyExamples")] 
    public class ValuesController : ApiController
    {
        [RouteAttribute("test5/{data}")]
        [AcceptVerbs("GET")]
        [ActionEditDataDebugActionWebApiFilter]
        public IEnumerable<string> Use4ActionsDebugAttribute(string data)
        {
            return new string[] { "value1", data };
        }
    }

The data parameter is changed in the pre-processing of the action filter. This is then changed and sent on to the action method and returned to the browser.

using System.Diagnostics;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace WebApi2Attributes.Attributes
{
    public class ActionEditDataDebugActionWebApiFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            object data = "";
            if(actionContext.ActionArguments.TryGetValue("data", out data))
            {
                data = data + "Injected!";
                actionContext.ActionArguments["data"] = data;
            }
            // pre-processing
            Debug.WriteLine("ACTION 1 DEBUG pre-processing logging");
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var objectContent = actionExecutedContext.Response.Content as ObjectContent;
            if (objectContent != null)
            {
                var type = objectContent.ObjectType; //type of the returned object
                var value = objectContent.Value; //holding the returned value
            }

            Debug.WriteLine("ACTION 1 DEBUG  OnActionExecuted Response " + actionExecutedContext.Response.StatusCode.ToString());
        }
    }
}

ActionFilters05

As shown above, the ActionFilter has intercepted the request data, added the Injected! text to the data and this is sent on to the action method which posts it back to the browser in the response.

ActionFilters Example 6:
This example shows how an ActionFilter can be used with base and parent controllers. The behavior is the same as a single controller.

using System.Collections.Generic;
using System.Web.Http;
using WebApi2Attributes.Attributes;

namespace WebApi2Attributes.Controllers
{
    [Class3DebugActionWebApiFilter]
    [Class4DebugActionWebApiFilter]
    [RoutePrefix("api/T2")] 
    public class BaseController : ApiController
    {
        [Action1DebugActionWebApiFilter]
        [Route("child")]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
    }
}
using System.Collections.Generic;
using System.Web.Http;
using WebApi2Attributes.Attributes;

namespace WebApi2Attributes.Controllers
{
    [Class1DebugActionWebApiFilter]
    [Class2DebugActionWebApiFilter]
    [RoutePrefix("api/T2")] 
    public class Values2Controller : BaseController
    {
        [Action1DebugActionWebApiFilter]
        [Route("parent")]
        public IEnumerable<string> GetParent()
        {
            return new string[] { "value1", "value2" };
        }

        [Action1DebugActionWebApiFilter]
        [OverrideActionFiltersAttribute]
        [Route("parent2")]
        public IEnumerable<string> GetParentOverride()
        {
            return new string[] { "value1", "value2" };
        }
    }
}

Results:
The parent controller attributes are decorated around the base controller attributes which are decorated around the action method attributes.

ActionFilters08

The override attribute disables all controller attributes, in the base controller and the parent controller.

Override in the parent controller: This has no effect whatsoever. This override is for action methods and not controllers.

And don’t forget:
System.Web.Http.Filters.ActionFilterAttribute != System.Web.Mvc.ActionFilterAttribute
The System.Web.Http.Filters.ActionFilterAttribute has no order, less events and can’t do as much as it’s cousin!

Filters: Using IoC and Property Injection

A resolver needs to be implemented to build up filters with the Web Api config when the filter has IoC dependent objects.

Firstly add Unity Web API Bootstrapper to your project.
ActionFilters06

Now create an ActionDescriptorFilterProvider class which implements the IFilterProvider interface. This will be used to resolve the ActionFilters.

using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Microsoft.Practices.Unity;

namespace WebApi2Attributes.App_Start
{
    public class WebApiUnityActionFilterProvider : ActionDescriptorFilterProvider, IFilterProvider
    {
        private readonly IUnityContainer container;

        public WebApiUnityActionFilterProvider(IUnityContainer container)
        {
            this.container = container;
        }

        public new IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
        {
            var filters = base.GetFilters(configuration, actionDescriptor);
            var filterInfoList = new List<FilterInfo>();

            foreach (var filter in filters)
            {
                container.BuildUp(filter.Instance.GetType(), filter.Instance);
            }

            return filters;
        }

        public  static void RegisterFilterProviders(HttpConfiguration config)
        {
            // Add Unity filters provider
            var providers = config.Services.GetFilterProviders().ToList();
            config.Services.Add(typeof(System.Web.Http.Filters.IFilterProvider), new WebApiUnityActionFilterProvider(UnityConfig.GetConfiguredContainer()));
            var defaultprovider = providers.First(p => p is ActionDescriptorFilterProvider);
            config.Services.Remove(typeof(System.Web.Http.Filters.IFilterProvider), defaultprovider);
        }
    }
}

Register the new filter provider in the WebApiConfig in the App_Start

using System.Web.Http;
using Microsoft.Practices.Unity.WebApi;
using WebApi2Attributes.App_Start;

namespace WebApi2Attributes
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            config.DependencyResolver = new UnityDependencyResolver(UnityConfig.GetConfiguredContainer());
 
            // Web API routes
            config.MapHttpAttributeRoutes();

            WebApiUnityActionFilterProvider.RegisterFilterProviders(config);

        }
    }
}

Now an attribute can be defined which uses Property Injection. Construction Injection cannot be used because the attribute requires a default constructor. The Dependency attribute from Unity requires that the property is not private.

using System.Diagnostics;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Microsoft.Practices.Unity;

namespace WebApi2Attributes.Attributes
{
    public class PropertyInjectionDebugActionWebApiFilter : ActionFilterAttribute
    {
        [Dependency]
        internal IDummyBusinessClass MyDummyBusinessClass { get; set; }

        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            Debug.WriteLine("ACTION 1 DEBUG pre-processing logging and IoC:" + MyDummyBusinessClass.GetSomething());
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var objectContent = actionExecutedContext.Response.Content as ObjectContent;
            if (objectContent != null)
            {
                var type = objectContent.ObjectType; //type of the returned object
                var value = objectContent.Value; //holding the returned value
            }

            Debug.WriteLine("ACTION 1 DEBUG  OnActionExecuted Response " + actionExecutedContext.Response.StatusCode.ToString());
        }
    }
}

The Unity objects need to be registered with Unity. This is done in the App_Start/UnityConfig class.

public static void RegisterTypes(IUnityContainer container)
{
  container.RegisterType<IDummyBusinessClass,  DummyBusinessClass>();
  container.RegisterType<PropertyInjectionDebugActionWebApiFilter>();       
}

Now the property injection attribute can be used in the controller.

        [PropertyInjectionDebugActionWebApiFilter]
        [RouteAttribute("test6")]
        [AcceptVerbs("GET")]
        [ActionEditDataDebugActionWebApiFilter]
        public IEnumerable<string> PropertyInjectionAttributeActionsDebugAttribute()
        {
            return new string[] { "value1" };
        }

Add when executed, it works as required.
ActionFilters07

Async methods for Action Filters
The ActionFilters also provide async virtual methods to override when you required long running tasks in your filter or require the async methods for other reasons. These virtual methods are now released in the version 2.1. See http://www.strathweb.com/2013/12/asp-net-web-api-2-1-rc-whats-new/

public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
  return base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);
}

public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
 return base.OnActionExecutingAsync(actionContext, cancellationToken);
}

Links:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/exception-handling

http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs

http://stackoverflow.com/questions/10941669/convert-custom-action-filter-for-web-api-use

http://stackoverflow.com/questions/10941669/convert-custom-action-filter-for-web-api-use

http://www.microsoft.com/en-us/download/confirmation.aspx?id=36476

http://msdn.microsoft.com/en-us/library/hh833994%28v=vs.108%29.aspx

http://www.dotnetcurry.com/showarticle.aspx?ID=888

http://weblogs.asp.net/imranbaloch/archive/2013/09/25/new-filter-overrides-in-asp-net-mvc-5-and-asp-net-web-api-2.aspx

http://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/

http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Http/Controllers/FilterGrouping.cs

http://hackwebwith.net/

http://www.strathweb.com/2013/12/asp-net-web-api-2-1-rc-whats-new/

6 comments

  1. Nice writeup!

  2. […] on how to update service to use ActionFilterAttribute to include related […]

  3. […] an ActionFilterAttribute, by calling GetClientCertificate on the request message (see some examples here). An action filter is an attribute that you can apply to a controller action — or an entire […]

  4. James · · Reply

    How can you facilitate enabling/disabling endpoints in web api2? For example you may want to disable an endpoint on production server but enable it on development server.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.