0

I'm trying to setup a brand new Sitecore 7.2 website and I'm looking to integrate MVC 5, Glass Mapper and Microsoft Unity as a DI container and Sitecore doesn't want to play very nice.

My scenario is this:

  • Got a Web project called PoC.Quotes.Web - this will contain only CSS, HTML and any other assets, no controllers
  • Got a class library project called PoC.Quotes.Controllers - this only contains controllers
  • Got a class library project called PoC.Quotes.DataLayer - this contain a interface ISitecoreRepository and it's concrete implementation SitecoreRepository

The SitecoreRepository class has a constructor that receives 1 single parameter, the Glass Mapper Context, and one of my controllers receives 1 single parameter in the constructor...the ISitecoreRepository.

Sitecore repository class:

public class SitecoreRepository : ISitecoreRepository
{
    ISitecoreContext sitecoreContext = null;

    public SitecoreRepository(ISitecoreContext context)
    {
        this.sitecoreContext = context;
    }
}

Controller class:

public class HomeController : Controller
{
    private ISitecoreRepository _repository;
    public HomeController(ISitecoreRepository repository)
    {
        this._repository = repository;
    }
}

Every time I run the project Sitecore throws an error saying that it cannot create a controller of type (PoC.Quotes.Controllers.HomeController, PoC.Quotes.Controllers). I guess it shows the fully qualified name because that's how I set it up in the controller rendering.

First problem is the controller constructor parameter. I took it out and use this statement to get the instance for the repository:

System.Web.Mvc.DependencyResolver.Current.GetService<ISitecoreRepository>();

The result is null, because the class SitecoreRepository is only having 1 constructor with 1 parameter and it won't get instantiated. Once I get that parameter out of the question too, then all works great.

However, to me this kinda defies the purpose of having a DI container.

I've tried looking at Castle Windsor, but although there is more documentation online, nothing works as I'm getting similar issues.

It is a bit annoying because if I run a similar test in a basic MVC 5 app (I did that just to be sure I'm not going mad), all works fine in less than 5 minutes.

Any ideas?

Edit:

In an interesting twist, after a few good hours spent on this issue, I've noticed that actually either Unity or Windsor containers work fine with one limitation...a big one.

In my controller rendering I've set the controller property as the fully qualified name of the controller:

PoC.Quotes.Controllers.HomeController, PoC.Quotes.Controllers

However, if I go in Sitecore and change that property to just Home, by magic all is good. I've even tried the interim version of using PoC.Quotes.Controllers.Home but still get an error, a different one mind you.

Not sure if I'm doing something wrong but it feels a bit odd.

Any ideas how to get this fixed?

Andrei U
  • 1,908
  • 1
  • 11
  • 14

2 Answers2

0

While I can't tell how your registrations are configured, it sounds like you might be better off with a controller factory. The example is Windsor but you could easily swap in Unity. So that you're not modifying the Global.asax, you can also use WebActivatorEx to wire up the bootstrapping startup.

Bootstrapper

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(Site.Website.Cms.WindsorConfig), "RegisterComponents")]`
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(Site.Website.Cms.WindsorConfig), "ReleaseComponents")]

/// <summary>
/// Provides a bootstrapping and resolving hook for dependency resolution for MVC, Web API, and service locator.
/// </summary>
public static class WindsorConfig
{
    private static Lazy<IWindsorContainer> _container;

    static WindsorConfig()
    {
        _container = new Lazy<IWindsorContainer>(() => BuildContainer());
    }

    public static IWindsorContainer WindsorContainer
    {
        get
        {
            return _container.Value;
        }
    }

    /// <summary>
    /// Generates and configures the container when the application is started.
    /// </summary>
    public static void RegisterComponents()
    {
        ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(WindsorContainer));
        GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new WindsorControllerActivator(WindsorContainer));
    }

    /// <summary>
    /// Disposes of the container when the application is shut down.
    /// </summary>
    public static void ReleaseComponents()
    {
        WindsorContainer.Dispose();
    }
}

Controller Factory

/// <summary>
/// Provides controller dependency resolving for ASP.NET MVC controllers.
/// </summary>
public class WindsorControllerFactory : DefaultControllerFactory
{
    private readonly IWindsorContainer _container;

    public WindsorControllerFactory(IWindsorContainer container)
    {
        if (container == null) throw new ArgumentNullException("container");
        this._container = container;
    }

    public override void ReleaseController(IController controller)
    {
        this._container.Release(controller);
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
        }

        return (IController)this._container.Resolve(controllerType);
    }
}
Jim Noellsch
  • 714
  • 4
  • 7
  • Hi Jim, thanks for your reply. My unity config is pretty simple, adding the configuration for the ISitecoreRepository interface and the ISitecoreContext (Glass Mapper) interface. I've tried previously Castle Windsor with a factory as you shown in your example, but got to the same issue. – Andrei U Oct 09 '15 at 06:55
  • Can you set a breakpoint in your bootstraping code to validate you've got the Global.asax or WebActivator working within Sitecore? – Jim Noellsch Oct 14 '15 at 15:56
  • After a few good hours of decompiling Sitecore.MVC I've arrived at the conclusion that the problem resides in the name I specify for the controller in Sitecore. If I just use "Home" as name, all is perfect. Once I use a fully qualified name, then Sitecore uses Reflection to instantiate a new object and looking for the parameterless constructor. I'm still looking into it as it seems to be a restriction around using fully qualified name that is ASP .NET MVC related. Any help or ideas are appreciated – Andrei U Oct 15 '15 at 15:40
  • Correct. For a rendering in Sitecore, you should **not** add the "Controller" suffix to the Controller field (a.k.a no fully qualified name). – Jim Noellsch Oct 20 '15 at 01:22
0

Spent a considerable amount of time on this and managed to come up with a solution. It was a bit long to write in here so I've put it on a blog post http://agooddayforscience.blogspot.co.uk/2015/10/sitecore-multi-tenancy-and-di-containers.html

Basically it wasn't an issue with Unity or other DI container, it was around how Sitecore handles fully qualified names. Yes, I know that ideally you don't want to use those but to follow the MVC pattern, but I've explained more in the blog post around why use fully qualified names.

As a high level explanation the problem resides in 2 Sitecore classes ControllerRunner and SitecoreControllerFactory. Both classes contain some methods that identify the fully qualified name and use reflection to call the parameter-less constructor to instantiate a new instance. The fix I've applied overrides these methods to call the controller factory regardless.

Thanks for all the help provided.

Andrei

Andrei U
  • 1,908
  • 1
  • 11
  • 14