Some new services were introduced in Glass Mapper 5 to make mapping Sitecore items easier than ever. In this post, I'll cover how to register all of these new services with the Sitecore dependency injection container.

The Configurator

Use this beastly configurator to register all Glass Mapper services with the Sitecore container.

public class GlassMapperConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    // For getting a SitecoreService for any database
    serviceCollection.AddSingleton<Func<Database, ISitecoreService>>(_ => CreateSitecoreService);

    // For injecting into Controllers and Web Forms
    serviceCollection.AddScoped(_ => CreateSitecoreContextService());
    serviceCollection.AddScoped(_ => CreateRequestContext());
    serviceCollection.AddScoped(_ => CreateGlassHtml());
    serviceCollection.AddScoped(_ => CreateMvcContext());
    serviceCollection.AddScoped(_ => CreateWebFormsContext());

    // For injecting into Configuration Factory types like pipeline processors
    serviceCollection.AddSingleton<Func<ISitecoreService>>(_ => Get<ISitecoreService>);
    serviceCollection.AddSingleton<Func<IRequestContext>>(_ => Get<IRequestContext>);
    serviceCollection.AddSingleton<Func<IGlassHtml>>(_ => Get<IGlassHtml>);
    serviceCollection.AddSingleton<Func<IMvcContext>>(_ => Get<IMvcContext>);
    serviceCollection.AddSingleton<Func<IWebFormsContext>>(_ => Get<IWebFormsContext>);
  }

  private static ISitecoreService CreateSitecoreService(Database database)
  {
    return new SitecoreService(database);
  }

  private static ISitecoreService CreateSitecoreContextService()
  {
    var sitecoreServiceThunk = Get<Func<Database, ISitecoreService>>();
    return sitecoreServiceThunk(Sitecore.Context.Database);
  }

  private static T Get<T>()
  {
    return ServiceLocator.ServiceProvider.GetService<T>();
  }

  private static IRequestContext CreateRequestContext()
  {
    return new RequestContext(Get<ISitecoreService>());
  }

  private static IGlassHtml CreateGlassHtml()
  {
    return new GlassHtml(Get<ISitecoreService>());
  }

  private static IMvcContext CreateMvcContext()
  {
    return new MvcContext(Get<ISitecoreService>(), Get<IGlassHtml>());
  }

  private static IWebFormsContext CreateWebFormsContext()
  {
    return new WebFormsContext(Get<ISitecoreService>(), Get<IGlassHtml>());
  }
}

Check out my post on scoped services in Sitecore if you're curious why all of the services are being registered twice. While you're there, grab the AddScopedWithFuncFactory<T> extension methods and save some code.

Patch it in like so:

<configuration>
  <sitecore>
    <services>
      <configurator type="YourAssembly.GlassMapperConfigurator, YourAssembly" />
    </services>
  </sitecore>
</configuration>

If you're still on Glass Mapper 4, you can use this same configurator. Wire up ISitecoreContext like IMvcContext and then remove the IRequestContext, IMvcContext, and IWebFormsContext registrations.

Using the Services

If you look at the configurator, you'll notice that I've registered ISitecoreService, IRequestContext, IMvcContext, and IWebFormsContext as scoped. The reasons for this are two fold:

  1. Each of the contexts are meant to wrap the context database. The context database is (generally) scoped to the request, so the wrapping contexts should be too. They definitely should not be singleton.
  2. SitecoreService implements IDisposable and all of the contexts depend on ISitecoreService. Sitecore automatically calls Dispose on all scoped services at the end of every request. If you use transient, you are responsible for calling Dispose at some point. Register as scoped and let Sitecore handle it for you.

Below I'll detail how to use these services in two different scenarios:

  1. In MVC (Controllers) and Web Forms (Pages/UserControls).
  2. In types instantiated by the Configuration Factory (e.g., pipeline processors).

Configuration Factory instantiated types need special attention, however. If you want to read details about why, check out my post on using scoped services with Sitecore.

ISitecoreService

ISitecoreService is your most basic entry point into Sitecore through Glass Mapper. Usually you should be using one of the contexts to work with Glass Mapper, but if you only need the methods provided by ISitecoreService, just use it.

The main reason I registered ISitecoreService (and IGlassHtml) with the container is so that the same instance of ISitecoreService will be shared across IRequestContext, IMvcContext, and IWebFormsContext if you require more than one of them in the same request.

Controller

public SampleController : Controller
{
  private readonly ISitecoreService _sitecoreService;

  public SampleController(ISitecoreService sitecoreService)
  {
    var _sitecoreService = sitecoreService ?? throw new ArgumentNullException(nameof(sitecoreService));
  }
  
  public ActionResult SampleAction()
  {
    // do something with _sitecoreService
    return View(model);
  }
}

Pretty standard--just inject ISitecoreService.

Configuration Factory Types (e.g., Pipeline Processors)

Instead of injecting ISitecoreService, you'll inject Func<ISitecoreService>. ⚠️Never directly inject ISitecoreService into a type instantiated by the Configuration Factory.⚠️ If you do inject ISitecoreService directly, you may get exceptions like this:

Glass.Mapper.MapperException: 'Service has been disposed, cannot create object'

This also applies if you want to inject an ISitecoreService into a service that you have registered as a singleton.

public SampleProcessor
{
  private readonly Func<ISitecoreService> _sitecoreServiceThunk;
  
  public SampleProcessor(Func<ISitecoreService> sitecoreServiceThunk)
  {
    _sitecoreServiceThunk = sitecoreServiceThunk ?? throw new ArgumentNullException(nameof(sitecoreServiceThunk));
  }
  
  public Process(PipelineArgs args)
  {
    var sitecoreService = _sitecoreServiceThunk();
    // do something with sitecoreService
  }
}

Here you're just injecting a method that will create the ISitecoreService for you. You do not need to dispose of the _sitecoreServiceThunk result, Sitecore will do that for you automatically at the end of the request.

ISitecoreService for Any Database

Sometimes you want to use Glass Mapper to map an item from a database other than the context database, like core. You'll do this the same way in Controllers and types instantiated by the Configuration Factory. However, ⚠️you always need to dispose it when you're done with it⚠️; Sitecore won't handle that for you at the end of the request.

public SampleProcessor
{
  private readonly BaseFactory _factory;
  private readonly Func<Database, ISitecoreService> _sitecoreServiceThunk;
  
  public SampleProcessor(BaseFactory factory, Func<Database, ISitecoreService> sitecoreServiceThunk)
  {
    _factory = factory ?? throw new ArgumentNullException(nameof(factory));
    _sitecoreServiceThunk = sitecoreServiceThunk ?? throw new ArgumentNullException(nameof(sitecoreServiceThunk));
  }
  
  public Process(PipelineArgs args)
  {
    var coreDatabase = _factory.GetDatabase("core");
    using (var sitecoreService = _sitecoreServiceThunk(coreDatabase))
    {
      // do something with sitecoreService
    }    
  }
}

The using statement will automatically dispose of the ISitecoreService for you.

Sure, you could just call new SitecoreService(coreDatabase) on line 15. However, if Glass Mapper is updated in the future with a new way to create ISitecoreService (e.g., through an ISitecoreServiceFactory), it's not going to be very much fun for you to go and replace all new SitecoreService(someDatabase) calls with SitecoreServiceFactory.Current.GetService(someDatabase). With the Func<Database, ISitecoreService> injection you'll just need to update one line in the GlassMapperConfigurator. Not to mention this is actually unit testable. Don't repeat yourself™.

If you like creating the ISitecoreService by database name instead of Database type, you can easily wire that up in your configurator too:

public void Configure(IServiceCollection serviceCollection)
{
  serviceCollection.AddSingleton<Func<string, ISitecoreService>>(_ => CreateSitecoreService));
}

private static ISitecoreService CreateSitecoreService(string databaseName)
{
  return new SitecoreService(databaseName);
}

Use it in your processor like this:

public SampleProcessor
{
  private readonly Func<string, ISitecoreService> _sitecoreServiceThunk;
  
  public SampleProcessor(Func<string, ISitecoreService> sitecoreServiceThunk)
  {
    _sitecoreServiceThunk = sitecoreServiceThunk ?? throw new ArgumentNullException(nameof(sitecoreServiceThunk));
  }
  
  public Process(PipelineArgs args)
  {
    using (var sitecoreService = _sitecoreServiceThunk("core"))
    {
      // do something with sitecoreService
    }    
  }
}

Then you don't need to inject BaseFactory. Up to you.

Glass Mapper Contexts

Working with IRequestContext, IMvcContext, and IWebFormsContext is really all the same. IRequestContext you generally use when you're not inside of a Controller or (shudder) Page/UserControl (i.e., Web Form)--this is what you'll most commonly be injecting into pipeline processors. IMvcContext and IWebFormsContext should only be injected into Controllers or Page/UserControls, respectively.

Controller

public SampleController : Controller
{
  private readonly IMvcContext _mvcContext;

  public SampleController(IMvcContext mvcContext)
  {
    var _mvcContext = mvcContext ?? throw new ArgumentNullException(nameof(mvcContext));
  }
  
  public ActionResult SampleAction()
  {
    // do something with _mvcContext
    return View(model);
  }
}

Again, in Controllers you can just request IMvcContext directly; no need for Func<IMvcContext>.

Web Forms

I'm sorry if you're still using Web Forms. Hope you can move on to MVC soon. You should only use ISitecoreService and IWebFormsContext in Pages and UserControls--don't use IMvcContext.

To inject into a Page:

[Sitecore.DependencyInjection.AllowDependencyInjection] 
public partial class SamplePage : Page 
{
  private readonly IWebFormsContext _webFormsContext;

  protected Sample()
  { 
  }

  public Sample(IWebFormsContext webFormsContext) 
  { 
     var _webFormsContext = webFormsContext ?? throw new ArgumentNullException(webFormsContext);
  }
  
  protected void Page_Load(object sender, EventArgs e)
  {
    // do something with _webFormsContext
  }
} 

To inject into a UserControl:

[Sitecore.DependencyInjection.AllowDependencyInjection] 
public partial class SampleControl : UserControl 
{ 
  protected SampleControl()
  {
  }
  
  public SampleControl(IWebFormsContext webFormsContext) 
  { 
     var _webFormsContext = webFormsContext ?? throw new ArgumentNullException(webFormsContext);
  }
  
  protected void Page_Load(object sender, EventArgs e)
  {
    // do something with _webFormsContext
  }
}

Note that in both instances you must have a default constructor. If you're curious why and how it works, check out this blog post. It's pretty clever.

If I recall correctly, services injected into Web Forms through controller injection are only available in Page_Load and later. If you try to access in earlier page events, such as Page_Init, you'll get a NullReferenceException. Unfortunately, if you need access to a service in Page_Init you'll just have to use service locator.

Configuration Factory Types (e.g., Pipeline Processors)

Notice that I inject Func<IRequestContext> into the SampleProcessor instead of IRequestContext. ⚠️Never directly inject IRequestContext, IMvcContext, or IRequestContext into a type instantiated by the Configuration Factory.⚠️

public SampleProcessor
{
  private readonly Func<IRequestContext> _requestContextThunk;
  
  public SampleProcessor(Func<IRequestContext> requestContextThunk)
  {
    _requestContextThunk = requestContextThunk ?? throw new ArgumentNullException(nameof(requestContextThunk));
  }
  
  public Process(PipelineArgs args)
  {
    var requestContext = _requestContextThunk();
    // do something with requestContext
  }
}

Don't forget to add the resolve="true" attribute to your processor when you add it to your config patch.

Again, this also applies to any services you have registered as singletons that need to depend on Glass Mapper contexts.

Unit Testing

If you're using dependency injection, hopefully you're unit testing. Unit testing ISitecoreService and the contexts is easy enough in Controllers, so I won't cover that here. For the examples below, I'll use Moq to mock dependencies.

ISitecoreService and Glass Mapper Contexts

Consider the following processor:

public SampleProcessor
{
  private readonly Func<IRequestContext> _requestContextThunk;
  
  public SampleProcessor(Func<IRequestContext> requestContextThunk)
  {
    _requestContextThunk = requestContextThunk ?? throw new ArgumentNullException(nameof(requestContextThunk));
  }
  
  public Process(PipelineArgs args)
  {
    var requestContext = _requestContextThunk();
    // do something with requestContext
  }
}

To set it up for unit testing:

public void Process_DoesSomeStuff_WhenCalled()
{
  // Arrange
  var mockRequestContext = new Mock<IRequestContext>();
  // set up the mock RequestContext
  var processor = new SampleProcessor(() => mockRequestContext.Object);
  var args = new PipelineArgs();
  
  // Act
  processor.Process(args);
  
  // Assert
  // assert that stuff worked
}

Nice and easy.

ISitecoreService with Any Database

Testing ISitecoreService when you need to pass in any database is easy too. Consider the following processor:

public SampleProcessor
{
  private readonly BaseFactory _factory;
  private readonly Func<Database, ISitecoreService> _sitecoreServiceThunk;
  
  public SampleProcessor(BaseFactory factory, Func<Database, ISitecoreService> sitecoreServiceThunk)
  {
    _factory = factory ?? throw new ArgumentNullException(nameof(factory));
    _sitecoreServiceThunk = sitecoreServiceThunk ?? throw new ArgumentNullException(nameof(sitecoreServiceThunk));
  }
  
  public Process(PipelineArgs args)
  {
    var coreDatabase = _factory.GetDatabase("core");
    using (var sitecoreService = _sitecoreServiceThunk(coreDatabase))
    {
      // do something with sitecoreService
    }    
  }
}

To unit test:

public void Process_DoesSomeStuff_WhenCalled()
{
  // Arrange
  var mockFactory = new Mock<BaseFactory>();
  mockFactory.Setup(f => f.GetDatabase(It.IsAny<string>()));
  
  var mockSitecoreService = new Mock<ISitecoreService>();
  // set up the mock SitecoreService
    
  var processor = new SampleProcessor(mockFactory, _ => mockSitecoreService.Object);
  var args = new PipelineArgs();
  
  // Act
  processor.Process(args);
  
  // Assert
  // assert that stuff worked
}

Again, nice and easy.

Conclusion

You'll notice I didn't cover use cases of IGlassHtml. I've seen it registered with dependency injection containers a lot before, but I've never actually seen a reason to use it. If you've got a good use case, let me know in the comments. Like with ISitecoreService, my main reason to register it with the container is just to make sure that IMvcContext and IWebFormsContext share the same instance in case both contexts are instantiated in the same request.