LibrarySites.Banner

Using Dependency Injection to Determine MVC Models with the Sitecore ASP.NET CMS

This blog post explains how you can use a dependency injection container such as Ninject to resolve model types at runtime using MVC with the Sitecore ASP.NET web Content Management System (CMS). This blog post explains how, but doesn’t even try to explain why, as I am not sure if there is any reason to use dependency injection for this. For more information about dependency injection with Sitecore, see the blog post Dependency Injection with the Sitecore ASP.NET CMS. For more information about how Sitecore determines models for MVC components, see the blog post How the Sitecore ASP.NET CMS Determines the Model Type for MVC Renderings. For more information about using MVC with Sitecore, see Posts about Using MVC with the Sitecore ASP.NET CMS.

I don’t want to spend more time on this than absolutely necessary, especially as various Sitecore implementations will choose their own dependency injection/inversion of control containers. This post doesn’t even describe how to get Ninject working, which you can read about elsewhere. To simplify the code and just demonstrate the pattern, the solution demonstrated in this post intentionally takes a shortcut that would be expensive at runtime: it builds the Ninject kernel each time Sitecore attempts to determines a model. I believe that a better solution would use an initialize pipeline processor that creates and populates a Ninject kernel only once, where that kernel would probably exist at application scope. Ninject.Web appears to provide an extension to enable such functionality, although I think a simple hack would just add a variable in global.asax. For more information about pipelines in Sitecore, see the blog post All About Pipelines in the Sitecore ASP.NET CMS.

There are two pieces to the solution presented here: an override to the Sitecore.Mvc.Presentation.ModelLocator class provided by Sitecore that determines types for MVC models, and a processor for the mvc.getModel pipeline that sets the ModelLocator property of the Sitecore.Mvc.Pipelines.Response.GetModel.GetModelArgs argument passed through that pipeline to an instance of that type (which contains code to populate the Ninject kernel).

The following sample code is for a ModelLocator override.  The new Kernel property creates and populates the Ninject kernel with some examples that are beyond trivial. The override of the GetModelFromTypeName() method uses that kernel.

namespace Sitecore.Sharedsource.Mvc.Presentation
{
  using SC = Sitecore;
  
  public class ModelLocator : SC.Mvc.Presentation.ModelLocator
  {
    Ninject.IKernel _kernel;
  
    private Ninject.IKernel Kernel
    {
      get
      {
        if (this._kernel == null)
        {
          this._kernel = new Ninject.StandardKernel();
          this._kernel.Bind<SC.Sharedsource.Mvc.Models.ItemViewModel>().To
            <SC.Sharedsource.Mvc.Models.SampleItemViewModel>();
          this._kernel.Bind<SC.Sharedsource.Mvc.Models.ContentItemViewModel>().To
            <SC.Sharedsource.Mvc.Models.SampleItemViewModel>();
        }
  
        return this._kernel;
      }
    }
  
    protected override object GetModelFromTypeName(
      string typeName, 
      string model, 
      bool throwOnTypeCreationError)
    {
      return Ninject.ResolutionExtensions.Get(
        this.Kernel, 
        SC.Reflection.ReflectionUtil.GetTypeInfo(typeName));
    }
  }
}

If an item instructs Sitecore to use ItemViewModel or ContentItemViewModel, this causes Sitecore to use the SampleItemViewModel instead. I think this means that the Model field in MVC presentation component definition items could specify interfaces, but again I am not sure if that has any value (unless maybe you’re already using a dependency injection container for this kind of thing). Note that Ninject.ResolutionExtensions.Get() is actually an extension method for Ninject.IKernel; I implemented this as a static method call because I think code is more clear outside of an IDE when I specify full namespaces rather than using directives.

The following code is for an mvc.getModel pipeline processor that attaches the ModelLocator override to the argument passed through the pipeline.

namespace Sitecore.Sharedsource.Mvc.Pipelines.Response.GetModel
{
  using SC = Sitecore;
  
  public class SetModelLocator : 
    SC.Mvc.Pipelines.Response.GetModel.GetModelProcessor
  {
    public override void Process(
      SC.Mvc.Pipelines.Response.GetModel.GetModelArgs args)
    {
      args.ModelLocator = new SC.Sharedsource.Mvc.Presentation.ModelLocator();
    }
  }
}

The following is a Web.config include file to enable that processor – note that it should come first in the pipeline. For information about Web.config include files, see the blog post All About web.config Include Files with the Sitecore ASP.NET CMS.

<configuration xmlns:patch="https://www.sitecore.com/xmlconfig/">
  <sitecore>
    <pipelines>
      <mvc.getModel>
        <processor 
          type="Sitecore.Sharedsource.Mvc.Pipelines.Response.GetModel.SetModelLocator, assembly"
          patch:before="processor[1]" />
      </mvc.getModel>
    </pipelines>
  </sitecore>
</configuration>

Using alternate dependency injection/inversion of control containers, moving the container somewhere less expensive to use, populating the container, and testing this entire solution are all beyond my level interest in this subject. I would however like to hear any comments about using dependency injection with MVC and Sitecore together.