Recently I had to update a Sitecore 7.2 site to use Helix architecture and bring some foundation modules in from a Sitecore 8.2 site.  There were several challenges with this process.  Here’s a few highlights:

Dependency Injection using Castle Windsor

For this site, Castle Windsor was being used for dependency injection.  In the Sitecore 8.2 site, dependencies are registered using a configurator in a .config patch.  This is not supported in Sitecore 7.2, so instead we have to do this in the Application_Start event in global.asax.cs.

In order to be able to use the same DI container throughout the code, I created a singleton container object (ContainerManager.Container):

public static class ContainerManager
 {
   private static IWindsorContainer _container;
   public static IWindsorContainer Container
   {
     get
     {
        if (_container != null) return _container;
        _container = new WindsorContainer();
        _container.Install(FromAssembly.This());
        return _container;
     }
   }
}

Then in global.asax.cs, I used that container to register the dependencies:

protected void Application_Start(object sender, EventArgs e)
 {
    _container = ContainerManager.Container;
    _container.Install(new RegisterGlassDependencies());
    ...

An example of the installer class for registering dependencies is shown below:

 public class RegisterGlassDependencies : IWindsorInstaller
 {
   public void Install(IWindsorContainer container, IConfigurationStore store)
   {
     container.Register(Component.For<ISitecoreContext>().ImplementedBy<SitecoreContext>());
     container.Register(Component.For<IGlassHtml>().ImplementedBy<GlassHtmlTemp>());
     container.Register(Component.For<IGlassFactory>().ImplementedBy<GlassFactory>());
   }
 }

In order for injected constructor parameters to resolve for Controllers, it is necessary to use a custom controller factory which uses the DI container to resolve the controller. There’s a fair bit of overriding going on, so here it comes.

Code for the controller factory shown below:

public class WindsorControllerFactory : DefaultControllerFactory
   {
       private readonly IWindsorContainer _container;
 
       public WindsorControllerFactory(IWindsorContainer container)
       {
           _container = container;
       }
 
       public override void ReleaseController(IController controller)
       {
           _container.Release(controller);
       }
 
       public override IController CreateController(RequestContext requestContext,string controllerName)
       {
           Assert.ArgumentNotNull(requestContext, "requestContext");
           Assert.ArgumentNotNull(controllerName, "controllerName");
           Type controllerType = null;
 
           if (TypeHelper.LooksLikeTypeName(controllerName))
           {
               controllerType = TypeHelper.GetType(controllerName);
           }
 
           if (controllerType == null)
           {
               controllerType = GetControllerType(
                   requestContext,
                   controllerName);
           }
 
           if (controllerType != null)
           {
               return (IController)_container.Resolve(controllerType);
           }
 
           return base.CreateController(requestContext, controllerName);
       }
   }

A pipeline was added with an “instead” patch for Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory.  This pipleine scans all assemblies and gets all classes based on IController then registers them with the Containermanager.Container.  Code shown below:

public class InitializeWindsorControllerFactory
   {
       public virtual void Process(ScapiPipelineArgs args)
       {
           SetupControllerFactory(args);
       }
 
       protected virtual void SetupControllerFactory(ScapiPipelineArgs args)
       {
           var container = ContainerManager.Container;
 
           //TODO: don't use hard-coded filter string
           var assemblies = GetAssemblies.GetByFilter("MyAssembly.*").Where(n => !n.FullName.StartsWith("MyAssembly.Service"))
               .Where(a => GetTypes.GetTypesImplementing<IController>(a).Any(x => x.Namespace != null && x.Namespace.StartsWith("MyNamespace")));
 
           foreach (var assembly in assemblies)
           {
               container.Register(Classes.FromAssembly(assembly).BasedOn<IController>().LifestyleTransient());
           }
 
           var controllerFactory = new WindsorControllerFactory(container);
 
           var scapiSitecoreControllerFactory = new
               ScapiSitecoreControllerFactory(controllerFactory);
 
           ControllerBuilder.Current.SetControllerFactory(scapiSitecoreControllerFactory);
       }
   }

Another pipeline was added with an “instead” patch for Sitecore.Mvc.Pipelines.Response.GetRenderer.GetControllerRenderer.  This pipeline allows us to use a custom controller renderer, as shown below:

public class GetControllerRenderer : Sitecore.Mvc.Pipelines.Response.GetRenderer.GetControllerRenderer
   {
       protected override Renderer GetRenderer(Rendering rendering, Sitecore.Mvc.Pipelines.Response.GetRenderer.GetRendererArgs args)
       {
           var renderer = base.GetRenderer(rendering, args);
           return !(renderer is ControllerRenderer) ? renderer : new CustomControllerRenderer(renderer as ControllerRenderer);
       }
   }

The custom controller renderer then allows us to use a custom controller runner as shown below:

public sealed class CustomControllerRenderer : ControllerRenderer
   {
       public CustomControllerRenderer(ControllerRenderer renderer)
       {
           ControllerName = renderer.ControllerName;
           ActionName = renderer.ActionName;
       }
 
       public override void Render(System.IO.TextWriter writer)
       {
           var controllerName = ControllerName;
           var actionName = ActionName;
           if (controllerName.IsWhiteSpaceOrNull() || actionName.IsWhiteSpaceOrNull())
           {
               return;
           }
           var controllerRunner = new CustomControllerRunner(controllerName, actionName);
           var value = controllerRunner.Execute();
           if (value.IsEmptyOrNull())
           {
               return;
           }
           writer.Write(value);
       }
 
   }

The custom controller runner then creates the controller using our custom controller factory with our DI container as a parameter.  This is our goal. Custom controller runner shown below:

public class CustomControllerRunner : ControllerRunner
   {
       public CustomControllerRunner(string controllerName, string actionName)
           : base(controllerName, actionName)
       { }
 
       protected override IController CreateController()
       {
           return CreateControllerUsingFactory();
       }
 
       private IController CreateControllerUsingFactory()
       {
           NeedRelease = true;
 
           var controllerFactory = new WindsorControllerFactory(ContainerManager.Container);
           return controllerFactory.CreateController(PageContext.Current.RequestContext, ControllerName);
       }
   }

There’s a few layers to it, but we got there in the end.

XUnit Tests

The tests that I moved over from the Sitecore 8.2 site used XUnit.  After moving them over, one of the errors I got in the Sitecore 7.2 site was:

Could not resolve type name: Sitecore.Data.DefaultDatabase, Sitecore.Kernel

To fix this,  for Sitecore versions prior to 8.2 all instances of ‘Sitecore.Data.DefaultDatabase, Sitecore.Kernel’ in the config files should be changed to ‘Sitecore.Data.Database, Sitecore.Kernel’.

One bit I got stuck on for a while was that there was a config file with this string in it that had not yet been added to the solution, but was there on the file system, and that was enough for this error to still be thrown.  It took a file system search for the string to track it down.

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s