Update 2
Thanks again for getting even more detailed. I haven't been able to get your GlobalFilterProvider to work correctly, but it seems that no matter which approach I take here, I won't be able to take advantage of the lifetime scoping that becomes available with a nested container. Currently, using the StructureMap.MVC5 NuGet package a nested container is created on Application_BeginRequest and disposed of on Application_EndRequest, which allows me to scope objects to UniquePerRequest and have them tore down when the nested container is disposed. It seems like no matter what a filter needs to get added on Application_Start and any dependencies still need to be added using the container, which on Application_Start is only the parent at that time. I don't even think using a Decoraptor will help me in this scenario.
Is there any way to achieve this?
This problem has been solved - see my other SO question.
Update 1 - Comments on NightOwl888's Answer
I understand what is happening in the "Filling Setter's of an Object" piece you linked to, as that code is what the StructureMapFilterProvider
is doing to build up the dependencies of the action filter. What I was trying to understand is the IContainer
reference in the StructureMapFilterProvider
. Is the current DependencyResolver
set in MVC what is doing the resolving of that IContainer
reference? I just want to understand where that is coming from. Also, adding .Singleton() did actually fix the problem, so thanks for pointing that out.
I'm still trying to understand that GlobalFilterProvider
you linked to, and I do understand the benefits of using constructor injection instead. I'm just trying to wrap my head around it all.
Original Post
I am using StructureMap for my DI needs, and have included it in my ASP.NET MVC 5 project via the StructureMap.MVC5
NuGet package. I have the typical logging action filter where I want to inject a dependency (ILogger) into the action filter I have created. While I have found and understand the passive attributes method for bypassing setter injection, I also became intrigued with the method of using a StructureMapFilterProvider
like so...
public class StructureMapFilterProvider : FilterAttributeFilterProvider
{
private readonly IContainer _container;
public StructureMapFilterProvider(IContainer container)
{
_container = container;
}
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(controllerContext, actionDescriptor);
foreach (var filter in filters)
{
_container.BuildUp(filter.Instance);
}
return filters;
}
}
However, the IContainer
dependency of the StructureMapFilterProvider
seems to be a little magical. My first question would be how does it even get resolved? What is doing the resolving if the container itself is what needs to be resolved. Second, it seems to have a problem with the nested containers the StructureMap.MVC5
setup creates and disposes of per request at Application_BeginRequest
and Application_EndRequest
.
On Application_Start
, the filter provider works fine, presumably because it's not a nested container yet, and the IContainer
instance resolved looks like this:
Yet if I run the same action again now that the application is started, the filter provider bombs and the IContainer instance now looks like this:
What am I doing wrong? Is there a fix for this?
The rest of my code is below if that helps.
Logger implementation:
public class Logger : ILogger
{
public void Log(string message)
{
Debug.WriteLine(message);
}
}
public interface ILogger
{
void Log(string message);
}
Logger action filter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class LoggerAttribute : ActionFilterAttribute
{
public ILogger Logger { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Logger.Log("Testing: Executing an action");
}
}
Additional DI setup added to DependencyResolution/IoC.cs class provided by the StructureMap.MVC5 package:
public static class IoC
{
public static IContainer Initialize()
{
var container = new Container(c =>
{
c.AddRegistry<DefaultRegistry>();
c.For<IFilterProvider>().Use<StructureMapFilterProvider>();
c.For<ILogger>().Use<Logger>();
c.Policies.SetAllProperties(p =>
{
p.OfType<ILogger>();
});
});
return container;
}
}
Controller:
public class HomeController : Controller
{
[Logger]
public ActionResult Index()
{
return Content("worked");
}
}