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