Adventures in Dependency Injection with Sitecore 8.1

Dependency Injection, specifically MVC controller injection, is stupid simple to set up in a vanilla ASP.NET MVC site. For you guys out there that are only working in Sitecore part of the time (or for the first time), however, it may seem like an impossible feature to set up. I mean, you don’t even have access to the Global.asax.cs in Sitecore (at least you shouldn’t), so how do you go about registering your dependencies? I’ll give you a hint: pipelines!

Lots of other guys have blogged about doing this, around doing it the right way, being multi-tenant friendly and have offered plenty of examples. The two I found extremely helpful are:

  1. Sean Holmesby: http://www.seanholmesby.com/safe-dependency-injection-for-mvc-and-webapi-within-sitecore/
  2. Pavel Veller: http://jockstothecore.com/to-the-controller-and-back-part-3-di-and-multitenancy/

So I wanted to talk through how this works, some of the nuances with the latest version of Sitecore and how you, too, can have controller injection in your Sitecore application.

The Pipeline

Sitecore has a ton of pipelines. Some of them fire when a page is requested, some fire when media is uploaded, some fire when publishing occurs. The one we want is called initialize, and (I believe) it fires during the Application_Start event. The initialize pipeline has a processor called InitializeControllerFactory. This processor uses the SitecoreControllerFactory class to instantiate controllers. Unfortunately, this class had a subtle change from Sitecore 8.0 to 8.1, which changes how we patch around this particular processor. If you check out SitecoreControllerFactory in Sitecore 8.0, it has code similar to this:

protected virtual IController CreateControllerInstance(RequestContext requestContext, string controllerName)
{
    if (Sitecore.Mvc.Extensions.StringExtensions.EqualsText(controllerName, this.SitecoreControllerName))
        return this.CreateSitecoreController(requestContext, controllerName);
    if (TypeHelper.LooksLikeTypeName(controllerName))
    {
        Type type = TypeHelper.GetType(controllerName);
        if (type != (Type) null)
            return TypeHelper.CreateObject<IController>(type);
    }
    return this.InnerFactory.CreateController(requestContext, controllerName);
}

However, in 8.1 this class has changed to run this code instead:

protected virtual IController CreateControllerInstance(RequestContext requestContext, string controllerName)
{
    if (Sitecore.Mvc.Extensions.StringExtensions.EqualsText(controllerName, this.SitecoreControllerName))
        return this.CreateSitecoreController(requestContext, controllerName);
    if (TypeHelper.LooksLikeTypeName(controllerName))
    {
        Type type = TypeHelper.GetType(controllerName);
        if (type != (Type) null)
        {
            IController controller = DependencyResolver.Current.GetService(type) as IController ?? TypeHelper.CreateObject<IController>(type);
            if (controller != null)
                return controller;
        }
    }
return this.InnerFactory.CreateController(requestContext, controllerName);
}

The important thing to note here is that in 8.0, MVC’s dependency resolver is not respected, but in 8.1 it is. This means that if you’re working in Sitecore 8.0, you’ll want to patch out the InitializeControllerFactory, but in 8.1 you’ll watch to patch after it.

Our Processor

Here’s the processor we’re going to add to our 8.1 instance. Of course, you should throw this into a patch file and place it somewhere in App_Config/Include. Lately I’ve been rolling with App_Config/Include/Tenant/MyPatch.config, but you should use what makes sense for you:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor type="Playground.Base.Pipelines.RegisterIoC, Playground.Base" patch:after="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory, Sitecore.Mvc']"/>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

We’ll follow Sean and Pavel’s lead as far as code goes. These guys got it right, and their implementation works. As far as IoC containers, I prefer both Unity and SimpleInjector. For our purposes, let’s go with SimpleInjector:

public class RegisterIoC
{
    public void Process(PipelineArgs args)
    {
        var container = GetDependencyContainer();

        System.Web.Mvc.IDependencyResolver chainedMvcResolver =
            new ChainedMvcResolver(new SimpleInjectorDependencyResolver(container),
                System.Web.Mvc.DependencyResolver.Current);
        System.Web.Mvc.DependencyResolver.SetResolver(chainedMvcResolver);

        System.Web.Http.Dependencies.IDependencyResolver chainedWebApiResolver =
            new ChainedWebApiResolver(new SimpleInjectorWebApiDependencyResolver(container),
                GlobalConfiguration.Configuration.DependencyResolver);
        System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = chainedWebApiResolver;
    }

    private SimpleInjector.Container GetDependencyContainer()
    {
        var container = new SimpleInjector.Container();

        // register dependencies as needed

        return container;
    }
}

// Adapted from: 
//  http://jockstothecore.com/to-the-controller-and-back-part-3-di-and-multitenancy/
//  http://www.seanholmesby.com/safe-dependency-injection-for-mvc-and-webapi-within-sitecore/
public class ChainedMvcResolver : System.Web.Mvc.IDependencyResolver
{
    IDependencyResolver _fallbackResolver;
    IDependencyResolver _newResolver;

    public ChainedMvcResolver(IDependencyResolver newResolver, IDependencyResolver fallbackResolver)
    {
        _newResolver = newResolver;
        _fallbackResolver = fallbackResolver;
    }

    public object GetService(Type serviceType)
    {
        object result = null;

        result = _newResolver.GetService(serviceType);
        if (result != null)
        {
            return result;
        }

        return _fallbackResolver.GetService(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        IEnumerable<object> result = Enumerable.Empty<object>();

        result = _newResolver.GetServices(serviceType);
        if (result.Any())
        {
            return result;
        }

        return _fallbackResolver.GetServices(serviceType);
    }
}

// the ChainedWebApiResolver.cs is also adapted from http://www.seanholmesby.com/safe-dependency-injection-for-mvc-and-webapi-within-sitecore/

As far as packages go for simple injector, you’ll want the following for Nuget:

  <package id="SimpleInjector" version="3.1.1" targetFramework="net45" />
  <package id="SimpleInjector.Extensions.ExecutionContextScoping" version="3.1.1" targetFramework="net45" />
  <package id="SimpleInjector.Integration.Web" version="3.1.1" targetFramework="net45" />
  <package id="SimpleInjector.Integration.Web.Mvc" version="3.1.1" targetFramework="net45" />
  <package id="SimpleInjector.Integration.WebApi" version="3.1.1" targetFramework="net45" />

 

Now here’s where the story gets interesting…

After I set this up locally and started playing around, everything seemed fine. I was able to create a dummy controller rendering which expected an interface, and I had my interface registered with SimpleInjector. Whenever I would use that controller rendering, my implementation would get injected in. “Great!” I thought, until I started poking deeper into Sitecore. Turns out other people faced the same issue I was facing: The silly AnalyticsDataController from Sitecore has more than one constructor!

Here’s my exact error from the Sitecore Launchpad:

Screen Shot 2016-03-16 at 8.57.20 PM

I dug around on the internet for a solution, but the best I could find was to register that controller with SimpleInjector directly, effectively tying your application to Sitecore 8. That was no good, because I was working on an enterprise layer to support multiple sites across multiple Sitecore versions. I kept referring back to Sean Holmesby’s post about how to set up IoC, and noticed he was using Castle Windsor to do the heavy lifting. When I compared it to my approach using SimpleInjector, I found the solution.

As it turns out, Castle Windsor uses a controller instantiation strategy to always construct objects using the greediest constructor it can fulfill. This is why Sean’s approach and demo doesn’t suffer from the dreaded AnalyticsDataController problem. To solve this, I ended up adding my own constructor resolution strategy to SimpleInjector, where if a controller has multiple constructors, I take the cheapest one.

public class SimplestConstructorBehavior : SimpleInjector.Advanced.IConstructorResolutionBehavior
{
    public ConstructorInfo GetConstructor(Type serviceType, Type implementationType)
    {
        return (from ctor in implementationType.GetConstructors()
                orderby ctor.GetParameters().Length ascending
                select ctor).FirstOrDefault();
    }
}

// and back in my container registration...
public class RegisterIoC
{
    // do stuff...

    private SimpleInjector.Container GetDependencyContainer()
    {
        var container = new SimpleInjector.Container();
        container.Options.ConstructorResolutionBehavior = new SimplestConstructorBehavior();

        // register dependencies as needed

        return container;
    }
}

And viola, Analytics is back online! And the best part? I don’t have to hardcode any silly dependencies!

Now for the ‘Gotcha’

Let’s assume we have a setup similar to this:

namespace Playground.Web.Areas.Playground.Controllers
{
    public class SanityCheckController : System.Web.Mvc.Controller
    {
        private IExampleService exampleService;

        public SanityCheckController(IExampleService exampleService)
        {
            this.exampleService = exampleService;
        }

        [HttpGet]
        public ActionResult DependencyResolution()
        {
            var model = new ExampleModel();

            if (exampleService == null)
            {
                model.Message = "Service was null";
            }
            else
            {
                model.Message = exampleService.GetMessage();
            }

            return PartialView(model);
        }
    }
}

With a Controller Rendering that looks like so:

ioc controller

With our example service looking something like:

public interface IExampleService
{
    string GetMessage();
}

public class ExampleService : IExampleService
{
    public string GetMessage()
    {
        return string.Format("Hello. The time is currently {0}", DateTime.Now.TimeOfDay);
    }
}

And within our pipeline we register dependencies like so:

private SimpleInjector.Container GetDependencyContainer()
{
    var container = new SimpleInjector.Container();
    container.Options.ConstructorResolutionBehavior = new SimplestConstructorBehavior();
    container.Register(typeof(IExampleService), typeof(ExampleService), new WebRequestLifestyle());
    return container;
}

This all looks good right?

Except when you add your new component to the screen, you get this error:

The controller for path '/' was not found or does not implement IController.

The solution? Well…it’s simple but the explanation is a little tricky. When Sitecore tries to resolve controllers, it will detect fully qualified controller names and try to resolve them using reflection. If the controller name is not fully qualified, it will defer to the MVC runtime. That, of course, will cause failures because Sitecore will try to construct controllers manually and our injected controllers do not have default constructors. The solution, if you happen to encounter this problem, is to create your controller renderings via convention (and NOT fully qualify them). In this case, ours would look like this:

ioc controller 2

This does break a few of our core principles around multi-tenancy, but it will circumvent the problem if you encounter it.

I hope this post helps in your Sitecore journey 🙂

Dylan McCurry

I am a certified Sitecore developer with a passion for the web. I hopped into the .NET space 5 years ago to work on enterprise-class applications and never looked back. I love building things—everything from from Legos to software that solves real problems. I have a strong foundation of backend skills, with sweet spots like security, portal solutions and APIs. Early on, before I had the benefit of SCORE, I made a lot of mistakes with Sitecore but learned a lot in the course of the struggle. I would like to support other developers by contributing my perspective on doing things “the Sitecore way,” rather than fighting the framework. Did I mention I love video games? Learn more about Dylan McCurry.

2 comments on Adventures in Dependency Injection with Sitecore 8.1

Sean HolmesbyMarch 30, 2016 - Reply

Hey Dylan,
Great post, and thanks for the mention.
I see Pavel got around the AnalyticsDataController issue by explicitly registering it. (http://jockstothecore.com/web-api-simpleinjector-and-analyticsdatacontroller/)
I’m yet to properly test this out, but am receiving contributions to the SimpleInjector part of the ChainedDependencyResolver code.
https://github.com/HedgehogDevelopment/sitecore-chained-dependency-resolver/tree/master/InversionOfControl.SimpleInjector

I suppose the difficult thing here is, when using Sitecore’s controllers…. how do we know when we should we be grabbing the cheapest one, and when should we be grabbing the greediest one?

I’m yet to really play around with this…. But I’ll definitely check it out soon.
Thanks again, this is a great post.
– Sean

Dylan McCurryMarch 30, 2016 - Reply

I think the simple answer is… we can’t be sure which one to construct. Through a little bit more code we could probably modify the constructor resolution strategy to determine the greediest that it can satisfy, but I think we would need to tie ourselves closer to Sitecore. I haven’t tried this myself, so I’m unsure of if it’s actually possible. Any idea why they chose to have multiple constructors on a controller in the first place?

To be honest, I haven’t heavily explored the analytics features using this setup… but everything appears to be OK 🙂

Add a Comment

Your email address will not be published. Required fields are marked *

Or request call back