Sitecore XP 8.1 with Solr/Glass/AutoFac (Part 1)

sitecore-solr-autofac

Over the past several weeks, I’ve been working my way through trying to get Sitecore 8.1 to work with the latest tools: Solr Server v6, Autofac v3.5, and Glass Mapper v4.  Turns out it was quite a journey and definitely in need of some blogging support.  This is the first part in a series of blog posts about the journey to get there.

Initial Setup

Before I begin, let’s review how this beast got started. I started off with a vanilla install of Sitecore 8.1 Update 2. (I haven’t upgraded to 8.1 Update 3 yet because of this work).

  • Sitecore 8.1 Update 2 – MVC
    • Distributed CM and CD
  • Team Development for Sitecore (TDS)
    • Used for T4 Code Generation and Serialization
  • Visual Studio Project with Web Publishing to my local instances
  • IIS / .NET 4.5
  • SQL Server 2014 Dev Edition

This blog is not intended to guide you through this setup.   I recommend using the Sitecore Instance Manager (SIM) Installer or the Sitecore installer to install Sitecore. For TDS see the recommended reading section at the end for some guidance links.

Disclaimer: I am NOT a front end developer and do not even pretend to be one. (I hire people for that stuff). So that’s why you don’t see the latest hipster tools like Gulp, SASS, or other front end tools listed.

Adding in Autofac

The first step is adding in Autofac to my Visual Studio project.

Step 1: Using NuGet Package Manager in Visual Studio, add Autofac to your Visual Studio project libraries and make sure that Autofac is registered as a reference in each project that you need it.

About Project Libraries

I personally like having libraries separated out for various purposes, so to setup forthcoming code snippets, I wanted to give you insight into how I’ve configured my project:

  • Domain Project
    • Contains Models, domain specific extension methods, and helpers.
    • I also store core business logic methods and class in here too unless having separate projects (for purposes of DI) makes more sense.
  • Framework Project
    • Contains integration classes and hooks for integrating various frameworks into the solution.
    • You’ll see code snippets from this library frequently in these blog posts.
  • Services Project
    • Where web service hooks and api classes are contained.
  • Website Project
    • And last but not least, the Website  project to store Sitecore configs, views, supporting front end files, etc.
  • And of course, then two TDS projects for Sitecore Master and Core databases.

Connecting Autofac to Sitecore

Cite: Major props to Nikola Gotsev for his very thorough blogging on how to wire in Autofac into Sitecore. It was heavily referenced (read: implemented) with some minor tweaks here and there.  It’s the minor tweaks, as well as some discussion that I’d like to highlight, but in doing so, I will put code snippets that look a lot like the code snippets on Nikola’s blog. All credit goes to him as well as Thomas Stern who originally came up with the approach used.

In simple .NET implementations of Autofac, the Dependency Resolver is registered in the Global.asax.cs. There’s a gist that Dan Solovay wrote that provides a nice highlight of that. However, with Sitecore MVC, I’d be implementing it half way, if Sitecore’s controllers weren’t being loaded into the container. Therefore, we want to make sure Dependency Resolver gets setup within the scope of not just ASP, but Sitecore as a whole so that Autofac is managing all of the controllers.

To get started, there are 3 major components to wiring up Autofac to Sitecore:

  1. Autofac Container Factory
  2. Autofac Controller Factory
  3. Custom Sitecore Initialize Pipeline

Autofac Container Factory

Step 2: Create and implement the Autofac Container Factory

The container factory is the primary location where the container gets built. Remember that in Dependency Injection we only want one container to manage all of our dependencies.  The container factory takes the place of registering within the Global.asax.cs.

using System;
using Autofac;
using Autofac.Integration.Mvc;

namespace SiteCoria.Framework.DependencyInjection.Autofac.Factory
{
    public class AutofacContainerFactory
    {
        public IContainer Create()
        {
            var builder = new ContainerBuilder();

            // Register All Controllers In The Current Scope
            builder.RegisterControllers(AppDomain.CurrentDomain.GetAssemblies());

            // Register Additional Controllers, Modules, etc
            //...

            //Build the Container
            return builder.Build();
        }
    }
}

One change from Nikola’s blog is that in Autofac 3.5+ and Sitecore 8.1, I ran into issues with registering the controllers using the “.InstancePerRequest” method.  Taking a deep dive into trying to figure out why this was erroring out, determined that I didn’t need the InstancePerRequest at all. So removing that allowed Sitecore to work appropriately. It’s entirely possible that there’s a monster lurking around the corner that I don’t know about, but thus far in all of my testing this approach has been rock solid.

Another important note: Take a look and see that this class is using references to the Autofac DLL from the NuGet Package. This will come into play a little later in the blog series, but keep track that by referencing Autofac, you are tying a specific version of Autofac to your solution.

Autofac Controller Factory

Step 3: Create and implement the Autofac Controller Factory

The controller factory has two functions: A) Controller Resolution, and B) Controller Release. As Nikola points out in his blog, in Autofac Managed Application, the release is not needed as Autofac manages that and releases the controller as needed on it’s own.

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace SiteCoria.Framework.DependencyInjection.Autofac.Factory
{
    public class AutofacControllerFactory : DefaultControllerFactory
    {

        protected override IController GetControllerInstance(RequestContext context, Type controllerType)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (controllerType == null)
            {
                throw new HttpException(404,
                    string.Format("No Controller Type Found For Path {0}",
                    context.HttpContext.Request.Path));
            }

            var controller = (IController)DependencyResolver.Current.GetService(controllerType);

            if (controller != null) return controller;

            throw new HttpException(404,
                string.Format("No Controller Type: {0} Found For Path {1}",
                controllerType.FullName,context.HttpContext.Request.Path));
        }

        public override void ReleaseController(IController controller)
        {
        }
    }
}

This is a pretty basic implementation of the DefaultControllerFactory class. I like how logic for handling null controller’s is exposed and offers up the ability to throw a 404 as shown here. Additional logging can easily be added here for more indepth analyzing of missing controllers or other wizardry.

One Big Change

The magic is really in the GetControllerInstance method. Now in Nikola’s implementation, his solution instantiates the controller factory by passing in the raw Autofac container that is created. Then in the GetControllerInstance uses that raw container for resolution instead of relying on the MVC Dependency Resolver.

However, that isn’t needed. In fact, there is an incredible blog post about How Not to do Dependency Injection by Paul Hiles.  If you are unclear about DI or IoC or even the difference between the two, I highly recommend reading this.

Custom Sitecore Initialize Pipeline

Step 4: Create and implement a Sitecore Pipeline Processor

The next step to implementing Autofac for Sitecore is to connect the container factory above to the Sitecore controller initialization. Create a Sitecore pipeline processor that sets the Dependency Resolver to our newly created Autofac container.

using System;
using System.Web.Mvc;
using Autofac.Integration.Mvc;
using SiteCoria.Framework.DependencyInjection.Autofac.Factory;
using Sitecore.Pipelines;

namespace SiteCoria.Framework.DependencyInjection.Sitecore.Pipelines
{
    public class InitializeAutofacControllerFactory
    {
        public virtual void Process(PipelineArgs args)
        {
            SetControllerFactory(args);
        }

        private void SetControllerFactory(PipelineArgs args)
        {
            if (args == null) throw new ArgumentNullException(nameof(args));
            var containerFactory = new AutofacContainerFactory();
            var container = containerFactory.Create();

            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

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

If you recall, in typical DI implementations, setting the DependencyResolver typically occurs very near to the Application Start method of the application (e.g. Global.asax.cs).  However, because we are creating a Sitecore initialize pipeline processor, it’s close enough.

This processor does three things:

  1. Creates and builds the Autofac IoC container.
  2. Sets the MVC Dependency Resolver  context.
  3. Initializes and sets the Sitecore ControllerBuilder context.

Step 5: Create Sitecore Patch Config to utilize new Controller Processor

This is the last step in setting up Autofac 3.5+ on Sitecore. It’s a pretty basic patch config file. We need to replace the Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory with our new Autofac controller factory.

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor type="SiteCoria.Framework.DependencyInjection.Sitecore.Pipelines.InitializeAutofacControllerFactory, SiteCoria.Framework" patch:before="*[@type='Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory, Sitecore.Mvc']" />
        <processor type="Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory, Sitecore.Mvc">
          <patch:delete />
        </processor>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

There you have it! Compile and deploy your solution and Sitecore MVC will now be running as a Autofac Managed Application.

Recommended Reading

Next Time….

In Part 2 of Sitecore XP 8.1 with Solr/Glass/AutoFac blog series, I’ll continue the project setup as I connect Glass Mapper into the solution.

Then, later on in this blog series, I’ll show how to configure Solr Server 6 into this series solution. I’ll show you how to tie all of the pieces together and give you a clue that the Sitecore Solr Support package is not all that it’s cracked up to be.

Ciao!

 

6 thoughts on “Sitecore XP 8.1 with Solr/Glass/AutoFac (Part 1)

  1. Hi Pete!

    I am trying to do almost the same that you did (I mean, configure Sitecore 8.1 with Solr and AutoFac), but I am facing issues with the “Sitecore Solr Support package” because it seems that Sitecore created the package using a really old version of Autofac (2.5.2.830). So I was wondering if you faced the same problem in your project or if you have any ideas about how to overcome this issue.

    Thanks in advance for any help on this.

    Like

    • Hi Luis, yes I did. I have full intentions of blogging about this in detail, but let me see if I can provide you some assistance. in this comment.

      The main offender with the versioning issue is the Sitecore.ContentSearch.SolrProvider.AutoFacIntegration DLL. This really doesn’t do anything for you, except expose the AutoFacSolrStartup class that is the Global.asax is supposed to be inherited from. I called mullarky on that.

      Instead, I decompiled that DLL, and ripped out that class, and made it a standalone class in my project.

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using Autofac;
      using Autofac.Extras.CommonServiceLocator;
      using AutofacContrib.SolrNet;
      using AutofacContrib.SolrNet.Config;
      using Microsoft.Practices.ServiceLocation;
      using Sitecore.ContentSearch.SolrProvider;
      using Sitecore.ContentSearch.SolrProvider.DocumentSerializers;
      using SolrNet;
      using SolrNet.Impl;
      using SolrNet.Schema;

      namespace SiteCoria.Framework.DependencyInjection.Solr
      {

      public class AutoFacSolrSitecoreConnector : ISolrStartUp
      {
          private readonly ContainerBuilder _builder;
          private readonly SolrServers _cores;
      
          public AutoFacSolrSitecoreConnector(ContainerBuilder builder)
          {
              if (!SolrContentSearchManager.IsEnabled)
                  return;
              _builder = builder;
              _cores = new SolrServers();
          }
      
          public void Initialize()
          {
              if (!SolrContentSearchManager.IsEnabled)
                  throw new InvalidOperationException(
                      "Solr configuration is not enabled. Please check your settings and include files.");
      
              foreach (var coreId in SolrContentSearchManager.Cores)
                  AddCore(coreId, typeof(Dictionary<string, object>),
                      SolrContentSearchManager.ServiceAddress + "/" + coreId);
      
              var container = RegisterSolrLocationProvider();
      
              ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(container));
      
              SolrContentSearchManager.SolrAdmin = BuildCoreAdmin();
              SolrContentSearchManager.Initialize();
          }
      
          public virtual IContainer RegisterSolrLocationProvider()
          {
              _builder.RegisterModule(new SolrNetModule(_cores));
      
              //Comment the following line if you're using version of SolrNet < 0.5.0.
              var ver = System.Reflection.Assembly.GetAssembly(typeof(Startup)).GetName().Version;
      
              if (ver.Major >= 0 && ver.Minor >= 5)
                  _builder.RegisterType<SolrFieldBoostingDictionarySerializer>().As<ISolrDocumentSerializer<Dictionary<string, object>>>();
      
              _builder.RegisterType<SolrSchemaParser>().As<ISolrSchemaParser>();
              _builder.RegisterType<HttpRuntimeCache>().As<ISolrCache>();
              foreach (SolrServerElement solrServerElement in _cores)
                  (_builder.RegisterType(typeof(SolrConnection))
                      .Named(solrServerElement.Id + typeof(SolrConnection), typeof(ISolrConnection))
                      .WithParameters(new[]
                      {
                          new NamedParameter("serverURL", solrServerElement.Url)
                      })).OnActivated(args =>
                      {
                          if (SolrContentSearchManager.EnableHttpCache)
                              ((SolrConnection)args.Instance).Cache = args.Context.Resolve<ISolrCache>();
                      });
      
              var container = _builder.Build();
              return container;
      
          }
      
          public void AddCore(string coreId, Type documentType, string coreUrl)
          {
              _cores.Add(new SolrServerElement
              {
                  Id = coreId,
                  DocumentType = documentType.AssemblyQualifiedName,
                  Url = coreUrl
              });
          }
          public bool IsSetupValid()
          {
              if (!SolrContentSearchManager.IsEnabled)
                  return false;
              var admin = BuildCoreAdmin();
              return
                  SolrContentSearchManager.Cores.Select(
                      defaultIndex => admin.Status().First())
                      .All(status => status.Name != null);
          }
      
          private ISolrCoreAdmin BuildCoreAdmin()
          {
              var solrConnection = new SolrConnection(SolrContentSearchManager.ServiceAddress);
      
              if (SolrContentSearchManager.EnableHttpCache)
                  solrConnection.Cache = ServiceLocator.Current.GetInstance<ISolrCache>() ?? new NullCache();
      
              return new SolrCoreAdmin(solrConnection, ServiceLocator.Current.GetInstance<ISolrHeaderResponseParser>(),
                 ServiceLocator.Current.GetInstance<ISolrStatusResponseParser>());
          }
      }
      

      }

      This now also will ask for a whole bunch of references. Add the current version references of Autofac, SolrNet, etc that you are wanting to use, add them to your library references, and then we are well on our way.

      Next, we need to activate this into our container.

      In the Global.asax, I registered the Autofac Solr Connector that I modified and pasted above.

          public void Application_Start()
          {
              //Register AutoFac Solr Sitecore Integration
              new AutoFacSolrSitecoreConnector(new ContainerBuilder()).Initialize(); // <------ RIGHT HERE
      
              AreaRegistration.RegisterAllAreas();
              FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
              RouteConfig.RegisterRoutes(RouteTable.Routes);
              BundleConfig.RegisterBundles(BundleTable.Bundles);
          }
      

      Finally, as stated in this blog post, just make sure that you add in the Initialize pipeline processor to activate and register the AutofacContainerFactory().

      Parting Thoughts:

      So the Initialize Method is setting the ServiceLocator.Provider to an AutofacServiceLocator container that has Solr in it.
      But the Sitecore Initialize pipeline processor is setting DependencyResolver. Technically two containers, but each serving a different purpose.

      Hope this helps.

      Like

  2. Oh and I did decompile all of the Sitecore Solr Provider DLL’s in to a Visual Studio project. (Thank you Jetbrains dotPeek) cleaned up some of the poor object decompiling, added in current versions of references, fixed Autoface 3.5’s breaking change to Module registration, and recompiled the DLL. Email me at pete at sitecore hacker dot come and I’ll send you the dll’s I compiled.

    Like

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com 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 )

Google+ photo

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

Connecting to %s