0

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.

Community
  • 1
  • 1
ryanulit
  • 4,983
  • 6
  • 42
  • 66
  • You can use a filter provider as in [this answer](http://stackoverflow.com/questions/36221865/questions-about-using-ninject/36224308#36224308) for all of your global filters. Then all you need to do is register the filter with the container with the right lifetime and it will resolve dependencies automatically. – NightOwl888 Jul 20 '16 at 14:38
  • @NightOwl888 but I still have to add my filter to the GlobalFilters collection and thus new up the filter with an instance of the dependency outside of the scope of the nested container. I don't see how that global filter provider gets around that issue. That is unless I misunderstand the approach and don't have to register it with GlobalFilters if I go that route. Is that the case? – ryanulit Jul 20 '16 at 14:41
  • That is correct. You can use the static global filters collection, but that would mean you are registering your filter as a singleton. If you use a filter provider instead, it will resolve on demand per request. So you can inject the container *once* into an infrastructure component that is part of your composition root, and each of your filters will get constructor-injected dependencies with any lifetime you like. – NightOwl888 Jul 20 '16 at 15:27
  • @NightOwl888 unfortunately that GlobalFilterProvider is not working exactly the way you have said to set it up. My needs seem to be a little different than that other question you referenced, as I am not replacing a default MVC implementation of the IAuthorizationFilter. Instead I just want to be able to use the nested container in an action filter. I've updated my question and was hoping you could fill in the remaining blanks. – ryanulit Jul 20 '16 at 17:09

1 Answers1

2

You can build a custom filter provider (as in this answer) to control the lifetime of the filters, rather than registering them in the static GlobalFilters.Filters collection.

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);
        }
    }
}

Usage

Keep in mind, MVC already contains lots of filter types. Even implementations of the base Controller type are registered as global filters, as Controller implements every type of filter. So, you need to be precise when you register your custom global filter types.

Option 1: Using convention based registration

// Register the filter provider with MVC.
FilterProviders.Providers.Insert(0, new GlobalFilterProvider());

Then in your DI registration

Scan(_ =>
{
    // Declare which assemblies to scan
    // In this case, I am assuming that all of your custom
    // filters are in the same assembly as the GlobalFilterProvider.
    // So, you need to adjust this if necessary.
    _.AssemblyContainingType<GlobalFilterProvider>();

    // Optional: Filter out specific MVC filter types
    _.Exclude(type => type.Name.EndsWith("Controller"));

    // Add all filter types.
    _.AddAllTypesOf<IActionFilter>();
    _.AddAllTypesOf<IAuthorizationFilter>();
    _.AddAllTypesOf<IExceptionFilter>();
    _.AddAllTypesOf<IResultFilter>();
    _.AddAllTypesOf<IAuthenticationFilter>(); // MVC 5 only
});

NOTE: You can control which filters MVC registers by changing the IFilterProvider instances that are registered.

enter image description here

So, an alternative could be something like:

FilterProviders.Providers.Clear();

// Your custom filter provider
FilterProviders.Providers.Add(new GlobalFilterProvider());

// This provider registers any filters in GlobalFilters.Filters
FilterProviders.Providers.Add(new System.Web.Mvc.GlobalFilterCollection());

// This provider registers any FilterAttribute types automatically (such as ActionFilterAttribute)
FilterProviders.Providers.Insert(new System.Web.Mvc.FilterAttributeFilterCollection());

Since the above code does not register the System.Web.Mvc.ControllerInstanceFilterProvider, the Controllers themselves won't be registered as global filters, so you wouldn't need to filter them out. Instead, you could simply let all of your controllers be registered as global filters.

// Optional: Filter out specific MVC filter types
// _.Exclude(type => type.Name.EndsWith("Controller"));

Option 2: Individual registration

// Register the filter provider with MVC.
FilterProviders.Providers.Insert(0, new GlobalFilterProvider());

Then in your DI registration

For<IActionFilter>().Use<MyGlobalActionFilter>();
For<IActionFilter>().Use<MyOtherGlobalActionFilter>();
Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • This looks great. Now because I am essentially overriding a structural concern of building filters that MVC handles by default, should I be worried that I might unintentionally break something? Especially if I exclude the Controller classes or remove any of the standard filter providers? Or even resolving a filter built into MVC using a nested container when it might need to live the whole application? – ryanulit Jul 21 '16 at 13:46
  • Thanks a ton @NightOwl888! You've been a tremendous help. I went with option 2. Going this route enabled me to limit the GlobalFilterProvider to only my custom IActionFilter implementations. I'll be adding my solution to my question soon. – ryanulit Jul 21 '16 at 15:40
  • If you take a look at the [ControllerInstanceFilterProvider source code](https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Mvc/ControllerInstanceFilterProvider.cs), you can see that it always loads up the current controller as a filter. So, it is definitely better to do that than to register and then load up every controller on every request. That said, you wouldn't technically be breaking anything by doing so, and as you can see these filter providers follow the SRP pretty strictly (as many parts of MVC do) and they are pretty easy to understand. – NightOwl888 Jul 21 '16 at 16:35