8

At the moment, I have a custom ControllerFactory into which I inject my Unity container:

in global.asax Application_Start():

var container = InitContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));

var factory = new UnityControllerFactory(container);
ControllerBuilder.Current.SetControllerFactory(factory);

In the controller factory I set my controllers to use a custom ActionInvoker like so:

protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
    var controller = base.GetControllerInstance(requestContext, controllerType) as Controller;

    if (controller != null)
        controller.ActionInvoker = new UnityActionInvoker(_container);

    return controller;
}

Finally in my custom ActionInvoker, I attempt to buildup actions being invoked using the ActionInvokers container:

protected override ActionExecutedContext InvokeActionMethodWithFilters(
        ControllerContext controllerContext,
        IList<IActionFilter> filters,
        ActionDescriptor actionDescriptor,
        IDictionary<string, object> parameters)
{
    var builtUpFilters = new List<IActionFilter>();

    foreach (IActionFilter actionFilter in filters)
    {
        builtUpFilters.Add(_container.BuildUp<IActionFilter>(actionFilter));
    }

    return base.InvokeActionMethodWithFilters(controllerContext, builtUpFilters, actionDescriptor, parameters);
}

Here is an example of one of the ActionFilters that is being built up:

public class PopulatRolesAttribute : ActionFilterAttribute, IActionFilter
{
    private const string RolesKey = "roles";

    [Dependency]
    public Func<IMetadataService> Service { get; set; }

    public PopulatRolesAttribute()
    {
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.Controller.ViewData[RolesKey] == null)
        {
            filterContext.Controller.ViewData[RolesKey] = Service().GetRoles();
        }
    }
}

The problem is that the public property on my custom ActionFilterAttribute is never injected with anything, it remains null on execution! I cannot see why my filter is not being correctly builtup by the container. The type being injected is registered properly, like so:

container.RegisterInstance(new ChannelFactory<IMetadataService>(
    new BasicHttpBinding(),
    new EndpointAddress("http://example.com/ABSApplication/MetadataService.svc")));

container.RegisterInstance<Func<IMetadataService>>(
    () => container.Resolve<ChannelFactory<IMetadataService>>().CreateChannel());

And is also being injected elsewhere in the application (Although not via .Buildup). This is pretty much the same process followed by this blog post. What piece of the puzzle am I missing?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Andrew Best
  • 175
  • 1
  • 1
  • 8
  • Out of curiosity, why not do this in say, authenticate request, preauth request, etc and cache them (session, etc) so they are there for each request while the user is logged in? – Adam Tuliper Nov 21 '11 at 00:26
  • My original intention was to encapsulate this functionality so that I could add it whenever an action required it. If upon review I was going to use the data retrieved by the filters a significant amount, I certainly would cache it globally for reuse throughout the application. It is actually beginning to look like that would be an easier solution anyway, regardless of reuse! I am still very curious as to why `container.BuildUp` does not work however. – Andrew Best Nov 21 '11 at 01:04
  • I did find this sample, going to check it out shortly. The source is available for download there as well http://msdn.microsoft.com/en-us/gg618494 – Adam Tuliper Nov 21 '11 at 06:03
  • I had seen that article, which spurred me to further questions [link](http://stackoverflow.com/questions/8199691/custom-actioninvoker-vs-custom-filterprovider-for-actionfilter-dependency-inject) here. Looking over the code in that article, it seems somewhat incomplete. I do not see where the custom IFilterProvider is hooked up. Surely just registering it in your container is not enough for it to be utilized. It does avoid the issue posted above using buildup. – Andrew Best Nov 21 '11 at 22:56
  • If I don't get a response soon regarding why BuildUp would not work in the original scenario, I will accept your answer, as I am grateful for the confirmation that I am thinking along the same lines as other devs would :) – Andrew Best Nov 21 '11 at 22:56
  • also I would consider doing this as an auth filter to make sure this runs first to populate everything or the roles may not be available for future controllers. – Adam Tuliper Nov 28 '11 at 22:41

1 Answers1

8

I would do this slightly differently. I would:

  1. install the unity.mvc3 nuget package

  2. call the bootstrapped.initialise() as mentioned in the txt doc the package adds to the project

  3. define your IMetadataService mapping in the initialize to your Concrete type

  4. add IMetadataService to your constructor

The difference between your implementation and the article you references is you use Func, which Im not sure if that adds a different problem to the mix here. I have to imagine it does as the above method (without Func) works fine for me.

Edit: Brad Wilson's code worked just fine for me here: http://bradwilson.typepad.com/blog/2010/07/service-location-pt4-filters.html

Applicable parts (please see the link above)

Global.asax.cs


protected void Application_Start() {
    // ...

    var oldProvider = FilterProviders.Providers.Single(
        f => f is FilterAttributeFilterProvider
    );
    FilterProviders.Providers.Remove(oldProvider);

    var container = new UnityContainer();
    var provider = new UnityFilterAttributeFilterProvider(container);
    FilterProviders.Providers.Add(provider);

    // ...
}

The filter itself:


using System;
using System.Web.Mvc;
using Microsoft.Practices.Unity;

public class InjectedFilterAttribute : ActionFilterAttribute {

    [Dependency]
    public IMathService MathService { get; set; }

    public override void OnResultExecuted(ResultExecutedContext filterContext) {
        filterContext.HttpContext.Response.Write(
            String.Format("

The filter says 2 + 3 is {0}.

", MathService.Add(2, 3)) ); } }

and UnityFilterAttributeFilterProvider.cs


using System.Collections.Generic;
using System.Web.Mvc;
using Microsoft.Practices.Unity;

public class UnityFilterAttributeFilterProvider : FilterAttributeFilterProvider {
    private IUnityContainer _container;

    public UnityFilterAttributeFilterProvider(IUnityContainer container) {
        _container = container;
    }

    protected override IEnumerable GetControllerAttributes(
                ControllerContext controllerContext,
                ActionDescriptor actionDescriptor) {

        var attributes = base.GetControllerAttributes(controllerContext,
                                                      actionDescriptor);
        foreach (var attribute in attributes) {
            _container.BuildUp(attribute.GetType(), attribute);
        }

        return attributes;
    }

    protected override IEnumerable GetActionAttributes(
                ControllerContext controllerContext,
                ActionDescriptor actionDescriptor) {

        var attributes = base.GetActionAttributes(controllerContext,
                                                  actionDescriptor);
        foreach (var attribute in attributes) {
            _container.BuildUp(attribute.GetType(), attribute);
        }

        return attributes;
    }
}

Adam Tuliper
  • 29,982
  • 4
  • 53
  • 71
  • 1
    I appreciate the direction to the Unity.Mvc3 package - it encapsulates dependency resolution for Mvc3 using Unity nicely. However I do not think it solves my issue. If I have a method in a controller decorated with a filter like so: `[PopulatRoles] public ActionResult Add() {` how do I achieve injection upon the PopulateRoles ActionFilterAttribute? I cannot use constructor injection as it is an attribute and I cannot utilize its constructor. Therefore I need to build up the attribute as it is created. – Andrew Best Nov 20 '11 at 22:38
  • As an addendum, I have seen that ninject does something completely different like this [link](http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/) to allow constructor injection on filters, however I am not sure if this is achievable with Unity. – Andrew Best Nov 20 '11 at 22:42
  • Filters are resolved internally in MVC I'll have to try a quick test here. – Adam Tuliper Nov 21 '11 at 00:13
  • If I add a constructor to my ActionFilterAttribute like so `public PopulateOfficesAttribute(Func service)` to achieve constructor injection, then I would need to supply that arg when using the attribute in code '[PopulateOffices(Arg)]', otherwise it will not compile. If I try use property injection, the MVC framework does not resolve the dependency within the ActionFilterAttribute for me - just tested it to make sure :) If you find it to be otherwise, let me know! – Andrew Best Nov 21 '11 at 00:54
  • 1
    actually this should work here - going to try it http://bradwilson.typepad.com/blog/2010/07/service-location-pt4-filters.html – Adam Tuliper Nov 28 '11 at 22:20
  • @AdamTuliper thanks for the pointer, you saved me some serious time! It looks like the code snippet here has been updated on the link you referenced to match changes to ASP NET that occurred later. – Norman H Sep 13 '12 at 18:09
  • 1
    Quite late to the party, but the `Unity Bootstrapper for ASP.NET MVC` package does all this work for you. – Stephen Collins Aug 10 '14 at 13:01