Setter injection not working with StructureMap in ASP.NET MVC authorization filter

1.2k Views Asked by At

We use a custom AuthorizeAttribute to handle a few aspects of user authorization. I need to add access to the database to check a value during authorization. This project uses the Repository pattern and Repos are all instantiated in controller constructors via StructureMap.

Unfortunately, it appears there's no way to use constructor injection with filters. I found an article by Jimmy Bogard (http://lostechies.com/jimmybogard/2010/05/03/dependency-injection-in-asp-net-mvc-filters/) explaining how to use property injection to handle this scenario. Basically, Jimmy's code intercepts the GetFilters method in ControllerActionInvoker and runs BuildUp on each filter to populate the properties. This was just what I needed, so I added the following class -

public class InjectingActionInvoker : ControllerActionInvoker
{
    private readonly IContainer _container;

    public InjectingActionInvoker(IContainer container)
    {
        _container = container;
    }

    protected override FilterInfo GetFilters(
        ControllerContext controllerContext, 
        ActionDescriptor actionDescriptor)
    {
        var info = base.GetFilters(controllerContext, actionDescriptor);

        info.AuthorizationFilters.ForEach(_container.BuildUp);
        info.ActionFilters.ForEach(_container.BuildUp);
        info.ResultFilters.ForEach(_container.BuildUp);
        info.ExceptionFilters.ForEach(_container.BuildUp);

        return info;
    }
}

And then wired it into StructureMap with these lines -

For<IActionInvoker>().Use<InjectingActionInvoker>();
For<ITempDataProvider>().Use<SessionStateTempDataProvider>();

Policies.SetAllProperties(c =>
{
    c.OfType<IActionInvoker>();
    c.OfType<ITempDataProvider>();
    c.WithAnyTypeFromNamespaceContainingType<UserProfileRepository>();
});

And finally, I added the public property to my custom AuthorizeAttribute class -

[SetterProperty]
public UserProfileRepository User { get; set; }

When I run the project and access a secure page, the AuthorizeCore code is hit twice. The first time, my property is set and working properly. However, the second call fails because the property is null. I set a breakpoint in the GetFilters method, and it's only being hit the first time. Unfortunately, I just don't have a strong enough understanding of StructureMap or Filters to know exactly where this is going sideways.

Below are the call stacks, in case that is useful for anyone -

Call #1

AppName.dll!AppName.Filters.SiteAuthorizeAttribute.AuthorizeCore(System.Web.HttpContextBase httpContext) Line 78    C#
[External Code] 
AppName.dll!AppName.Filters.SiteAuthorizeAttribute.OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext) Line 31   C#
[External Code]

Call #2

AppName.dll!AppName.Filters.SiteAuthorizeAttribute.AuthorizeCore(System.Web.HttpContextBase httpContext) Line 69    C#
[External Code] 
AppName.dll!AppName.Filters.SiteAuthorizeAttribute.OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext) Line 31   C#
[External Code] 
App_Web_1fnmflat.dll!ASP._Page_Views_Shared__Menu_cshtml.Execute() Line 2   C#
[External Code] 
App_Web_1fnmflat.dll!ASP._Page_Views_Shared__Layout_cshtml.Execute() Line 51    C#
[External Code] 

Any StructureMap masters care to share some wisdom? Thanks in advance for any help!

Edit: Here's the code for the _Menu.cshtml file -

@(Html.Kendo().Menu()
      .Name("Menu")
      .Items(items =>
      {
          items.Add().Text("My Dashboard").Action("Dashboard", "Home");
          items.Add().Text("My Account").Action("Edit", "Account");
          items.Add().Text("Purchase/Renew").Action("Index", "Purchase");
          items.Add().Text("Administration")
              .Items(children =>
              {
                  children.Add().Text("Accounts").Action("Index", "UserProfile");
                  children.Add().Text("Coupons").Action("Index", "Coupon");
              });
          items.Add().Text("Logout").Action("Logout", "Logon");
      })
      )

Thanks to some prompting from NightOwl888, I have isolated the issue to the Kendo Menu call. If I place a breakpoint on the final line of _Menu.cshtml and step into, I see DoGetInstance called for my HomeController. As soon as that is completed, OnAuthorization is fired for the second time and my repo property is null.

Anyone know what I'm missing here?

4

There are 4 best solutions below

0
On BEST ANSWER

Sadly, I haven't been able to track down what is happening. So here's how I've "fixed" the issue for now -

    [SetterProperty]
    public UserProfileRepository User
    {
        get { return _user ?? DependencyResolver.Current.GetService<UserProfileRepository>(); }
        set { _user = value; }
    }

When the injection works the injected value is used, otherwise I use the DependencyResolver to do the dirty work by hand. Not pretty, but I have no other options right now.

1
On

Since you know that GetFilters is not being called during the second call and the first instance of AuthorizeAttribute is working, then this clearly isn't an issue with your DI setup. If the AuthorizeAttribute instance were being cached, then all of the dependencies would be cached as well, so that isn't it either.

The issue boils down to the fact that your menu isn't calling the FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor); method before using the AuthorizeAttribute. I can only guess as to why though because you didn't provide the code for your menu or its partial method.

You could try overriding ControllerActionInvoker.InvokeAuthorizationFilters instead.

2
On

I too was facing the same issue and was trying different methods, finally this worked for me after removing [AllowMultiple = true] and now AuthorizeCore is called once. I am curious how this is linked to AuthorizeCore.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CompanyAuthorizeAttribute : AuthorizeAttribute
{}

before it was

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class CompanyAuthorizeAttribute : AuthorizeAttribute
{}
0
On

I did not get this to work with a custom filter provider, as described in this thread:

How can you unit test an Action Filter in ASP.NET Web Api?

I tried a bunch of options, but ended up injecting a dependency resolving function into my attributes. That way, unit tests can inject a function that returns a fake, mock or whatever, while the application can inject a function that resolves the dependency using the IoC container.

I wrote a short blog post about how I got it to work. Would love to hear what you think of it and if it helped you out:

http://danielsaidi.com/blog/2015/09/11/asp-net-and-webapi-attributes-with-structuremap