Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I do dependency injection into action filters in ASP.NET 4 RC WebAPI?

I'm using Windsor to manage IoC for my controllers in a WebAPI project. I've got a DependencyResolver working nicely to resolve controller dependencies, but now I'm looking to inject dependencies into a custom action filter I'm using to manage authentication.

I've looked into using a custom ActionInvoker but it's not clear from the interface that WebAPI is using how I would go about resolving property dependencies on the custom action filter attribute before it executes. Anyone have a good example of how to do this in the MVC 4 RC?

EDIT: I'm aware you can't do constructor injection on filters, because they're attributes and therefore instantiated by the .NET framework - but I'm hoping there's some point in the execution lifecycle that happens AFTER the filter is instantiated but BEFORE it gets executed, where I could run some custom code to enumerate across the filters' public properties and inject the necessary services.

like image 756
Dylan Beattie Avatar asked Jun 11 '12 11:06

Dylan Beattie


2 Answers

Action filters are attributes. In .NET attribute the instantiation process is managed by the .NET runtime and you don't have control over it. So one possibility is to use Poor Man's Dependency Injection which I would personally advice you against.

Another possibility is to use a marker attribute:

public class MyActionFilterAttribute : Attribute 
{ 

}

and then have the action filter using constructor injection:

public class MyActionFilter : ActionFilterAttribute
{
    private readonly IFoo _foo;
    public MyActionFilter(IFoo foo)
    {
        _foo = foo;
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (actionContext.ActionDescriptor.GetCustomAttributes<MyActionFilterAttribute>().Any())
        {
            // The action is decorated with the marker attribute => 
            // do something with _foo
        }
    }
}

and then register it as a global action filter in Application_Start:

IFoo foo = ....
GlobalConfiguration.Configuration.Filters.Add(new MyActionFilter(foo));
like image 58
Darin Dimitrov Avatar answered Sep 22 '22 19:09

Darin Dimitrov


I had the same problem, but decided to go for the ServiceLocator (DependencyResolver.GetService) for this, as its in the framework it seems to me to be a valid approach

public class RequiresSessionAttribute :
    ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var sessionService =
            (ISessionService) actionContext
                    .ControllerContext.Configuration.DependencyResolver
                    .GetService(typeof (ISessionService));

        var sessionId = HttpUtility
            .ParseQueryString(actionContext.Request.RequestUri.Query)
            .Get("sessionId");

        if (sessionId == null
            || !sessionService.IsValid(sessionId))
            throw new SessionException();

        base.OnActionExecuting(actionContext);
    }
}

and here is a test for this attribute, bit of a pain but possible

public class requires_sessionId
{
    [Fact]
    void can_call_action_with_session_id()
    {
        var context = GetContext("http://example.com/?sessionId=blaa");

        var sut = new RequiresSessionAttribute();

        Assert.DoesNotThrow(
            () => sut.OnActionExecuting(context));
    }

    [Fact]
    void can_not_call_action_without_session_id()
    {
        var context = GetContext("http://example.com/");

        var sut = new RequiresSessionAttribute();

        Assert.Throws<SessionException>(
            () => sut.OnActionExecuting(context));
    }

    HttpActionContext GetContext(string url)
    {
        var sessionServiceMock = new Mock<ISessionService>();
        sessionServiceMock
            .Setup(x => x.IsValid(It.IsAny<string>()))
            .Returns(true);

        var dependancyResolverMock = new Mock<IDependencyResolver>();
        dependancyResolverMock
            .Setup(x => x.GetService(It.IsAny<Type>()))
            .Returns(sessionServiceMock.Object);

        var config = new HttpConfiguration
               {
                   DependencyResolver = dependancyResolverMock.Object
               };
        var controllerContext = new HttpControllerContext
               {
                    Configuration = config,
                    Request = new HttpRequestMessage(
                               HttpMethod.Get,
                               url)
                };

        return
            new HttpActionContext
                {
                    ControllerContext = controllerContext,
                };
    }
}
like image 22
Anthony Johnston Avatar answered Sep 22 '22 19:09

Anthony Johnston