Search code examples
asp.net-mvcdependency-injectionstructuremapaction-filterstructuremap3

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


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?


Solution

  • 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.