Final Solution
With help from @NightOwl888's answer, here's the final approach I went with for anyone who ends up here:
1) Added the global filter provider:
public class GlobalFilterProvider : IFilterProvider
{
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var nestedContainer = StructuremapMvc.StructureMapDependencyScope.CurrentNestedContainer;
foreach (var filter in nestedContainer.GetAllInstances<IActionFilter>())
{
yield return new Filter(filter, FilterScope.Global, order: null);
}
foreach (var filter in nestedContainer.GetAllInstances<IAuthorizationFilter>())
{
yield return new Filter(filter, FilterScope.Global, order: null);
}
foreach (var filter in nestedContainer.GetAllInstances<IExceptionFilter>())
{
yield return new Filter(filter, FilterScope.Global, order: null);
}
foreach (var filter in nestedContainer.GetAllInstances<IResultFilter>())
{
yield return new Filter(filter, FilterScope.Global, order: null);
}
foreach (var filter in nestedContainer.GetAllInstances<IAuthenticationFilter>())
{
yield return new Filter(filter, FilterScope.Global, order: null);
}
}
}
2) Registered it in the FilterProviders collection:
public static void Application_Start()
{
// other bootstrapping code...
FilterProviders.Providers.Insert(0, new GlobalFilterProvider());
}
3) Added a custom filter using the passive attributes approach:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class SomeAttribute : Attribute
{
}
public class SomeFilter : IActionFilter
{
private readonly ISomeDependency _dependency;
public SomeFilter(ISomeDependency dependency)
{
_dependency = dependency;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.ActionDescriptor.GetCustomAttributes(true).OfType<SomeAttribute>().Any())
return;
_dependency.DoWork();
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
4) Then wired everything up in StructureMap (in this solution the SomeAttribute and GlobalFilterProvider classes are in the same "Filters" folder within the root folder):
public class ActionFilterRegistry : Registry
{
public ActionFilterRegistry()
{
Scan(s =>
{
// find assembly containing custom filters
s.AssemblyContainingType<GlobalFilterProvider>();
// limit it to the folder containing custom filters
s.IncludeNamespaceContainingType<GlobalFilterProvider>();
// exclude any of the Attribute classes that contain metadata but not the behavior
s.Exclude(type => type.IsSubclassOf(typeof(Attribute)));
// register our custom filters
s.AddAllTypesOf<IActionFilter>();
});
}
}
Original Post
I'm currently using a nested container per request with StructureMap in an ASP.NET MVC 5 application. I'm utilizing the structuremap.mvc5
nuget package to setup all the DI infrastructure for me (the dependency resolver, wiring up the container and creating and disposing of the nested container on App_BeginRequest
and App_EndRequest
). I'm at the point now where I need to do some DI within action filters in order to automate some functionality. After a good amount of research, I am attempting to do so without the need for setter injection, using Mark Seemann's passive attributes approach.
All seemed well and good while building the attribute and filter, until I got to registering the filter with the global filter collection within App_Start. I have a dependency that I would like to be created only once per request so that not only the action filter, but also other non-filter infrastructure classes utilized during a request, can use the same instance of that dependency over the entire request. If the nested container were resolving the dependency, it would do that by default. However, because I have to register the new filter in App_Start, I don't have access to the nested container.
For example, my global.asax:
public class MvcApplication : System.Web.HttpApplication
{
public static StructureMapDependencyScope StructureMapDependencyScope { get; set; }
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
var container = IoC.Initialize(); // contains all DI registrations
StructureMapDependencyScope = new StructureMapDependencyScope(container);
DependencyResolver.SetResolver(StructureMapDependencyScope);
// filter uses constructor injection, so I have to give it an instance in order to new it up,
// but nested container is not available
GlobalFilters.Filters.Add(new SomeFilter(container.GetInstance<ISomeDependency>()));
}
protected void Application_BeginRequest()
{
StructureMapDependencyScope.CreateNestedContainer();
}
protected void Application_EndRequest()
{
HttpContextLifecycle.DisposeAndClearAll();
StructureMapDependencyScope.DisposeNestedContainer();
}
protected void Application_End()
{
StructureMapDependencyScope.Dispose();
}
}
Does anyone know how to solve this? I've come across the decoraptor solution as well via this other SO question, but using an abstract factory within my filter would just create a new instance of the dependency, rather than using the single per request instance I want.
The only other solution I have come up with was to use setter injection with a custom filter provider that uses the static StructureMapDependencyScope instance created in the global, like this:
public class StructureMapFilterProvider : FilterAttributeFilterProvider
{
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(controllerContext, actionDescriptor);
foreach (var filter in filters)
{
MvcApplication.StructureMapDependencyScope.CurrentNestedContainer.BuildUp(filter.Instance);
}
return filters;
}
}
While that seems to work alright, it just seems a little dirty.