Search code examples
asp.net-mvc-5sitecoreinversion-of-controlcastle-windsorglass-mapper

Sitecore 7 IoC and Controller Renderings


I'm using GlassMapperin my SC7 project. It configures Castle Windsor IoC container "out of the box", allowing to add a custom configuration. I added some dependencies to my controller constructor and overwrote the default SitecoreControllerFactory with one using Castle's container to resolve them. I added the controller as Controller Rendering, but it doesn't seem to work.

Diving into it, I realized that the default controller factory is not being called at all, though it's configured correctly. Instead, Sitecore seems to go for an alternative pipeline, calling a Sitecore.Mvc.Controllers.ControllerRunner which uses reflection to load the controller and create an instance of it, which throws an exception since the constructor needs the dependencies.

Here's the code.

Initialize:

public class Initialize
{
    public void Process(PipelineArgs args)
    {
        ViewEngines.Engines.Clear();
        ViewEngines.Engines.Add(new WebFormViewEngine());
        ViewEngines.Engines.Add(new ThemedRazorEngine());

        GlassMapperSc.Start();

        SetupControllerFactory(args);
    }

    public virtual void SetupControllerFactory(PipelineArgs args)
    {
        var container = Utils.Ioc.Ioc.CurrentContainer;
        if (container == null)
        {
            return;
        }

        var controllerFactory = new WindsorControllerFactory(container);

        var sitecoreControllerFactory = new SitecoreWindsorControllerFactory(controllerFactory);
        ControllerBuilder.Current.SetControllerFactory(sitecoreControllerFactory);
    }
}

Sitecore Controller Factory:

public class SitecoreWindsorControllerFactory : SitecoreControllerFactory
{
    public SitecoreWindsorControllerFactory(IControllerFactory innerFactory) : base(innerFactory)
    {
    }

    protected override IController CreateControllerInstance(RequestContext requestContext, string controllerName)
    {
        return controllerName.EqualsText(SitecoreControllerName) ? CreateSitecoreController(requestContext, controllerName) : InnerFactory.CreateController(requestContext, controllerName);
    }
}

Default Controller factory:

public class WindsorControllerFactory : DefaultControllerFactory
{
    private const int PageNotFoundStatus = 404;

    private readonly IWindsorContainer _container;

    public WindsorControllerFactory(IWindsorContainer container)
    {
        _container = container;
    }

    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
        // todo: wrap this in the final implementation
        if (!TypeHelper.LooksLikeTypeName(controllerName))
        {
            return base.CreateController(requestContext, controllerName);
        }
        var type = TypeHelper.GetType(controllerName);

        return type != null ? GetControllerInstance(requestContext, type) : null;
    }

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

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            // todo: the request context should be wrapped in final implementation.
            throw new HttpException(
                PageNotFoundStatus,
                string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
        }

        return _container.Resolve(controllerType) as IController;
    }
}

And the controller:

ublic class LanguagePickerController : Controller
{
    private readonly ISitecoreContext _context;

    public LanguagePickerController()
    {
        _context = Ioc.CurrentContainer.Resolve<ISitecoreContext>();
    }

    public ActionResult LanguagePickerIndex()
    {
        //var context = new SitecoreContext();
        var dataSource = RenderingContext.Current.Rendering.DataSource;
        var model = _context.GetItem<LanguagePicker>(dataSource);

        // ReSharper disable once Mvc.ViewNotResolved
        return View("LanguagePicker",model);
    }
}

You can't see the dependency in the constructor since now I'm resolving it calling the container directly.


Solution

  • Well, the solution took a while but finally I got it: I overwrote 5 classes: GetControllerRenderer, ControllerRenderer, ControllerRunner, ControllerFactory and SitecoreControllerFactory.

    In GetControllerRenderer, method GetRenderer calls the ControllerRenderer, in which method Render calls ControllerRunner. In ControllerRunner I overrode method CreateController to bypass Sitecore code which load the controller with reflection and use a new controller factory based on Castle Windsor.