Starting with Sitecore 8.2, Sitecore has shipped with the .NET Core Dependency Injection container for dependency injection. As my friend Akshay Sura wrote on his blog, the performance of this container rivals Simple Injector, and today it's my preferred container when working with Sitecore. Plenty of posts have been written about working with dependency injection in Sitecore (read them all if you're just getting started), but in this post I'll cover how to work with a service lifetime that is finicky in Sitecore: Scoped.

TL;DR

If you want to inject scoped services into anything created by the Sitecore Configuration Factory (e.g., pipeline processors), register them with the container like this:

public class ExampleConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    serviceCollection.AddScoped<IScopedService, ScopedService>();
    serviceCollection.AddSingleton<Func<IScopedService>>(_ => () => ServiceLocator.ServiceProvider.GetService<IScopedService>());
  }
}

Or grab the AddScopedWithFuncFactory extension methods to register like this:

public class ExampleConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    serviceCollection.AddScopedWithFuncFactory<IScopedService, ScopedService>();
  }
}

In Configuration Factory instantiated classes that depend on your scoped service, inject like this:

public class ExampleProcessor
{
  private readonly Func<IScopedService> _scopedServiceThunk;
  
  public ExampleProcessor(Func<IScopedService> scopedServiceThunk)
  {
    _scopedServiceThunk = scopedServiceThunk ?? throw new ArgumentNullException(nameof(scopedServiceThunk));
  }
  
  public void Process(PipelineArgs args)
  {
    var scopedService = _scopedServiceThunk();
    scopedService.DoSomething();
    scopedService.DoSomethingElse();
  }
}

Note that this only applies to types instantiated by the Configuration Factory. Scoped services can get injected into your Controllers as IScopedService; Func<IScopedService> isn't necessary.

Service Lifetimes

All containers that I've used, including the .NET Core Dependency Injection container, support at a minimum three service scopes, or lifetimes:

  1. Singleton
  2. Scoped
  3. Transient

Singleton

Singleton is pretty self-explanatory. Any service registered as a singleton will only be created once during the lifetime of the application. That is, one instance of that type will be created, and that same type will be shared between all objects that depend on it. In general, this should be your go-to scope for best performance (but keep singleton services stateless!). In Sitecore you'd register like this:

public class ExampleConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    serviceCollection.AddSingleton<ISingletonService, SingletonService>();
  }
}

Singleton services will only be disposed once the application shuts down.

Transient

Transient is the opposite of singleton. Every time a transient service is requested, a new instance of that service will be created. When would you want this? Your MVC and Web API controllers are good examples--you want a brand new MVC controller for every controller rendering on the page. In Sitecore you'd register a transient service like this:

public class ExampleConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    serviceCollection.AddTransient<ITransientService, TransientService>();
  }
}

Transient services are not disposed automatically. Most (if not all) containers work this way. Be careful if you register a transient service that implements IDisposable--you need to dispose it yourself or you will cause memory leaks on your site.

Scoped

The focus of this post--the scoped lifetime--falls right inbetween singleton and transient. Services that are registered as scoped are instantiated once per request. It doesn't always have to be per request--you can define scopes however you like (e.g., per thread, your own scope), but in Sitecore (and web applications in general), "scoped" generally means request. In Sitecore you'd register a scoped service like this:

public class ExampleConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    serviceCollection.AddScoped<IScopedService, ScopedService>();
  }
}

Scoped services that implement IDisposable are automatically disposed at the end of each request.

Mixing Scopes

You're going to eventually register services of different scopes in your application. However, you need to be careful of the scopes of dependencies, otherwise you'll introduce captive dependencies that can cause difficult-to-diagnose bugs. Consider the following configurator:

public class ExampleConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    serviceCollection.AddSingleton<ISingletonService, SingletonService>();
    serviceCollection.AddScoped<IScopedService, ScopedService>();
    serviceCollection.AddTransient<ITransientService, TransientService>();
  }
}

And the following classes:

public class ScopedService : IScopedService, IDisposable
{
  private bool _disposed;

  public void DoSomething()
  {
    if (_disposed) throw new Exception($"{nameof(ScopedService)} has been disposed.");
  }

  public void Dispose()
  {
   Dispose(true);
   GC.SuppressFinalize(this);
  }
  
   protected virtual void Dispose(bool disposing)
   {
      if (_disposed) return; 
      _disposed = true;
   }
}

public class SingletonService : ISingletonService
{
  private readonly IScopedService _scopedService;
  
  public SingletonService(IScopedService scopedService)
  {
    _scopedService = scopedService ?? throw new ArgumentNullException(nameof(scopedService));
  }
  
  public void DoSomething()
  {
    _scopedService.DoSomething();
  }
}

As I mentioned above, singletons live for the entire application, but scoped services are disposed at the end of each request. On the first request to your application, the SingletonService will get created, ScopedService will get created, and SingletonService will do its job happily. At the end of the request, ScopedService will get disposed--it can't be used anymore. On the second request, the SingletonService from the first request will get reused, and it'll throw an exception--it's trying to use a disposed object.

To avoid this issue follow this rule:

Services of shorter scopes can only depend on services of longer or equivalent scopes.

That means:

  1. Singleton services can only depend on other singleton services.
  2. Scoped services can only depend on singleton and other scoped services.
  3. Transient services can depend on any service scope.

In fact, one of Simple Injector's killer features is that it will check for violations of the above rules when your application starts and throw an exception if it detects any.

You can work around this limitation with the service locator, but it's an anti-pattern:

public class SingletonService : ISingletonService
{  
  public void DoSomething()
  {
    var scopedService = ServiceLocator.ServiceProvider.GetService<IScopedService>();
    scopedService.DoSomething();
  }
}

The above implementation will never have the disposal problems, because IScopedService isn't cached in the SingletonService. However, you're not going to enjoy the ceremony required to unit test this class.

Scoped Services in Sitecore

Dependency Injection and the Configuration Factory

Dependency Injection in Sitecore 8.2 was really exciting because it meant we could finally use constructor injection with types instantiated by the Configuration Factory (e.g., pipeline processors). Over were the days when we had to use service locator to inject dependencies. If you've not used constructor injection with a pipeline processor before, this is how you do it:

public SampleProcessor
{
  private readonly ISingletonService _singletonService;
  
  public SampleProcessor(ISingletonService singletonService)
  {
    _singletonService = singletonService ?? throw new ArgumentNullException(nameof(singletonService));
  }
}

Then in config add the resolve="true" attribute to your <processor /> node:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <anyPipeline>
        <processor type="SampleAssembly.SampleProcessor, SampleAssembly"
                   resolve="true" />
      </anyPipeline>
    </pipelines>
  </sitecore>
</configuration>

Don't forget that attribute or you'll get an exception like this:

Could not create instance of type: YourAssembly.YourProcessorType. No matching constructor was found.

This is an awesome feature, but there's one problem: most types instantiated by the Configuration Factory are singletons, pipeline processors included. Kevin Brechbühl wrote a nice post about this. So this seemingly limits constructor injection to only singleton services. Bummer.

In Kevin's post, Nick "Techphoria 414" Wesselman points out that if you add the reusable="false" attribute to a <processor />, it will be transient instead of singleton. Putting aside the fact that you probably shouldn't do this for performance reasons (especially in a pipeline like <httpRequestBegin />), now you've got a way to support all service lifetimes in pipeline processors.

Except scoped.

Scoped Services and the Configuration Factory

As of Sitecore 9.1 Update-1 (all the way back to Sitecore 8.2 Initial Release), scoped services do not work with the Configuration Factory. If you add reusable="false" to a pipeline processor and inject a scoped service into it, the scoped service will get treated as a singleton. Yes, it sounds crazy, but it's easy to test. Register the following service as scoped and inject it into an <httpRequestBegin /> processor with reusable="false":

public ScopedService
{
  public Guid Id = Guid.NewGuid();
}

Put a breakpoint on Id and watch its value over several requests--it'll always be the same. If this were behaving as a scoped service, its value would be different between each request. For even more fun, inject this service into a Controller and notice that the instance injected into your Controller has a different Id than the instance injected into the processor! 🤯

If you dive into the Sitecore.Kernel with dotPeek, you'll see that the Sitecore.Configuration.DefaultFactory has an instance of IServiceProvider injected into it. When Sitecore starts, the IServiceProvider that gets injected here is the root service provider. What do I mean by root service provider? Sitecore MVP Dmytro Shevchenko touches on this in one of his many answers on Sitecore Stack Exchange. In pseudocode, this is how the .NET Core Dependency Injection container works in Sitecore at a high level:

// Application starts
using (var rootProvider = new ServiceProvider())
{
  var configFactory = new DefaultFactory(rootProvider);
  var serviceScopeFactory = new ServiceScopeFactory(rootProvider);

  // Request begins
  // SitecorePerRequestScopeModule HTTP module creates a new scope
  using (var scopedProvider1 = serviceScopeFactory.CreateScope())
  {
    // controllers use scopedProvider to get services
    // ServiceLocator.ServiceProvider uses scopedProvider to get services
    // Configuration Factory types use rootProvider to get services
  } // all services created by scopedProvider disposed
  // Request ends

  // Request begins
  // SitecorePerRequestScopeModule HTTP module creates a new scope
  using (var scopedProvider2 = serviceScopeFactory.CreateScope())
  {
    // controllers use scopedProvider to get services
    // ServiceLocator.ServiceProvider uses scopedProvider to get services
    // Configuration Factory types use rootProvider to get services
  } // all services created by scopedProvider disposed
  // Request ends

} // all services created by rootProvider disposed
// Application shuts down

Singletons are always created by the root provider (even when requested through a scoped provider). Scoped services get their "scoped" functionality by being created and disposed by a scoped provider. When the Configuration Factory is instantiated, it's provided a copy of the root provider, so everything injected by the Configuration Factory is effectively scoped to the application. The concept of a scoped service doesn't work--services registered as scoped behave as singletons with the root provider.

This has the unfortunate consequence that any IDisposible scoped services resolved by the container won't be disposed until Sitecore shuts down which could result in memory leaks or other issues.

Injecting Scoped Services with the Configuration Factory

You can register almost any type with the .NET Core Dependency Injection container. This means that you can register Func<T> (i.e., a method), and the container can inject that Func<T> (method) into classes for you. We can take advantage of this to safely inject scoped services with the Configuration Factory.

Registering a Scoped Service

As I mentioned earlier, you can use ServiceLocator.ServiceProvider.GetService<T>() in your pipeline processor Process method to safely get access to scoped services. You can use the ServiceLocator.ServiceProvider to inject scoped services by registering them twice like this:

public class ExampleConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    serviceCollection.AddScoped<IScopedService, ScopedService>();
    serviceCollection.AddSingleton<Func<IScopedService>>(_ => () => ServiceLocator.ServiceProvider.GetService<IScopedService>());
  }
}

Line 6 looks a little wild so let's break it down.

A Func<T> is just a method--it's a method that takes no parameters and returns an object of type T. Here's an example:

Func<int> someOtherMethod = SomeOtherMethod;
var result = someOtherMethod();
Console.WriteLine(result); // 4

public int SomeOtherMethod()
{
  return 2 + 2;
}

Written more succinctly with a lambda:

Func<int> someOtherMethod = () => 2 + 2;
var result = someOtherMethod();
Console.WriteLine(result); // 4

So serviceCollection.AddSingleton<Func<IScopedService>>(...) just means that we are registering a method that takes no parameters and returns an object of type IScopedService with the container.

Now let's look at what's inside the ..., because it probably looks like it wouldn't even compile: _ => () => ServiceLocator.ServiceProvider.GetService<IScopedService>().

99% of the time when you register services with serviceCollection, you're doing it like this: serviceCollection.AddSingleton<ISingletonService, SingletonService>(). However, instead of providing SingletonService as the second type parameter, you can actually pass in a factory function to AddSingleton, AddScoped, and AddTransient. So if you wanted, you could write serviceCollection.AddSingleton<ISingletonService, SingletonService>() as:

serviceCollection.AddSingleton<ISomeInterface>(serviceProvider => {
  var someDependency = serviceProvider.GetService<ISomeDependency>();
  return new SomeImplementation(someDependency);
});

When you pass a factory function, it always takes serviceProvider as a parameter. Another way to write the above would be:

serviceCollection.AddSingleton<ISomeInterface>(CreateSomeImplementation);

private static ISomeInterface CreateSomeImplementation(IServiceProvider serviceProvider)
{
  var someDependency = serviceProvider.GetService<ISomeDependency>();
  return new SomeImplementation(someDependency);
}

This is a handy feature, but beware: the serviceProvider that is passed into the AddSingleton, AddScoped, and AddTransient methods may be the root provider!!!!!!!! This means you never want to use it to resolve a scoped service. Which brings us back to this lambda: _ => () => ServiceLocator.ServiceProvider.GetService<IScopedService>().

The _ is just shorthand for I don't care about this parameter. I could've written serviceProvider => () => ServiceLocator.ServiceProvider.GetService<IScopedService>, but since I'm not using the serviceProvider parameter, I prefer to write _ => .... Instead, we delegate to ServiceLocator.ServiceProvider to get the service, because it's always going to use the scoped provider under the hood. The () => ServiceLocator.ServiceProvider.GetService<IScopedService>() portion of the lambda is the actual Func<IScopedService> being returned.

The Func<IScopedService> type is registered as a singleton for two reasons: performance and flexibility. It doesn't have state, so there's no need to create the method more than once (performance). It's registered as a singleton so that it can be injected into a service of any scope (flexibility).

If all of these lambdas feel a bit too cryptic, you could rewrite the ExampleConfigurator as follows:

public class ExampleConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    serviceCollection.AddScoped<IScopedService, ScopedService>();
    serviceCollection.AddSingleton(ScopedServiceThunk);
  }
  
  private static Func<IScopedService> ScopedServiceThunk(IServiceProvider serviceProvider)
  {
    return GetScopedService;
  }
  
  private static IScopedService GetScopedService()
  {
    return ServiceLocator.ServiceProvider.GetService<IScopedService>();
  }
}

Notice on line 11 that I'm returning GetScopedService instead of GetScopedService()--that's because I want to return the GetScopedService method, not the result of the method.

With all of this, we can now inject Func<IScopedService> into services.

Use the Scoped Service

To use your scoped service, just take Func<IScopedService> as a constructor parameter (don't forget to add resolve="true" when you patch the processor in):

public class ExampleProcessor
{
  private readonly Func<IScopedService> _scopedServiceThunk;
  
  public ExampleProcessor(Func<IScopedService> scopedServiceThunk)
  {
    _scopedServiceThunk = scopedServiceThunk ?? throw new ArgumentNullException(nameof(scopedServiceThunk));
  }
  
  public void Process(PipelineArgs args)
  {
    var scopedService = _scopedServiceThunk();
    scopedService.DoSomething();
    scopedService.DoSomethingElse();
  }
}

Then in your Process() method just call _scopedService() and under the hood it'll call ServiceLocator.ServiceProvider.GetService<IScopedService>() for you. You can use this same trick to inject transient dependencies into scoped or singleton services, too.

This is technically no different than the following, except that we've inverted control of the ScopedServiceThunk method to the instantiator of the processor:

public class ExampleProcessor
{  
  public void Process(PipelineArgs args)
  {
    var scopedService = ScopedServiceThunk();
    scopedService.DoSomething();
    scopedService.DoSomethingElse();
  }
  
  private static IScopedService ScopedServiceThunk()
  {
    return ServiceLocator.ServiceProvider.GetService<IScopedService>();
  }
}

Outside of the Configuration Factory, you don't need to do this. MVC and Web API aren't affected by this issue, so you can inject your service directly without the indirection provided by the Func.

Register Easily with Extension Methods

Registering scoped services twice is a pain. Fortunately, extension methods can get rid of that ceremony entirely. Add the following class to your solution (maybe in a foundation Dependency Injection module):

public static class ServiceCollectionExtensions
{
  public static IServiceCollection AddScopedWithFuncFactory<TService, TImplementation>(
    this IServiceCollection services)
    where TService : class
    where TImplementation : class, TService
  {
    return services.AddWithFuncFactory<TService, TImplementation>(ServiceLifetime.Scoped);
  }

  public static IServiceCollection AddScopedWithFuncFactory<TService>(
    this IServiceCollection services,
    Func<IServiceProvider, TService> factory)
    where TService : class
  {
    return services.AddWithFuncFactory(factory, ServiceLifetime.Scoped);
  }

  public static IServiceCollection AddTransientWithFuncFactory<TService, TImplementation>(
    this IServiceCollection services)
    where TService : class
    where TImplementation : class, TService
  {
    return services.AddWithFuncFactory<TService, TImplementation>(ServiceLifetime.Transient);
  }

  public static IServiceCollection AddTransientWithFuncFactory<TService>(
    this IServiceCollection services,
    Func<IServiceProvider, TService> factory)
    where TService : class
  {
    return services.AddWithFuncFactory(factory, ServiceLifetime.Transient);
  }

  private static IServiceCollection AddWithFuncFactory<TService, TImplementation>(
    this IServiceCollection services,
    ServiceLifetime lifetime)
    where TService : class 
    where TImplementation : class, TService
  {
    if (services == null) throw new ArgumentNullException(nameof(services));
    if (!Enum.IsDefined(typeof(ServiceLifetime), lifetime))
      throw new InvalidEnumArgumentException(nameof(lifetime), (int) lifetime, typeof(ServiceLifetime));
    
    services.Add(new ServiceDescriptor(typeof(TService), typeof(TImplementation), lifetime));
    return services.AddFuncFactory<TService>();
  }

  private static IServiceCollection AddWithFuncFactory<TService>(
    this IServiceCollection services,
    Func<IServiceProvider, TService> factory,
    ServiceLifetime lifetime)
    where TService : class
  {
    if (services == null) throw new ArgumentNullException(nameof(services));
    if (factory == null) throw new ArgumentNullException(nameof(factory));
    if (!Enum.IsDefined(typeof(ServiceLifetime), lifetime))
      throw new InvalidEnumArgumentException(nameof(lifetime), (int)lifetime, typeof(ServiceLifetime));

    services.Add(new ServiceDescriptor(typeof(TService), factory, lifetime));
    return services.AddFuncFactory<TService>();
  }

  private static IServiceCollection AddFuncFactory<TService>(this IServiceCollection services)
    where TService : class
  {
    if (services == null) throw new ArgumentNullException(nameof(services));
    services.AddSingleton<Func<TService>>(_ => () => ServiceLocator.ServiceProvider.GetService<TService>());
    return services;
  }
}

Now registering a scoped service with Sitecore is as easy as this:

serviceCollection.AddScopedWithFuncFactory<IScopedService, ScopedService>();

Or for a factory function:

serviceCollection.AddScopedWithFuncFactory<HttpRequestBase>(_ => HttpContext.Current.Request);

And with that my friends, as Chief Inspector Clouseau would say, the case is sol-ved.

Why Inject Func<T>?

Why do this instead of using service locator? Aside from it being an anti-pattern, it becomes pretty clear with unit testing. Let's go back to the service locator example:

public class ExampleProcessor
{  
  public void Process(PipelineArgs args)
  {
    var scopedService = ServiceLocator.ServiceProvider.GetService<IScopedService>();
    scopedService.DoSomething();
    scopedService.DoSomethingElse();
  }
}

In a unit testing context, how are you going to get a mock of IScopedService into the ExampleProcessor? Sure, you can go through the ceremony of setting up the ServiceLocator.ServiceProvider, but look at how easy it is with the Func<T> method:

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

That's nice and easy!

Practical Scoped Service Examples

What are some things you'd commonly want to wire up as scoped? Here are a few ideas:

  • HttpRequestBase so you don't have to mess with HttpContext.Current.Request.
  • HttpResponseBase for the same reason above.
  • HttpSessionBase for the same reason above.
  • A wrapper class for Tracker.Current.Contact.
  • Glass Mapper contexts: IMvcContext, IRequestContext, etc.

Note that HttpContextBase is already registered with the container out of the box as a transient service in Sitecore (nice find by Michael "Sitecore Junkie" Reynolds). Do not re-register it as a scoped service or you'll break stuff.

Conclusion

It's interesting to note that Habitat (it's not a starter kit!) doesn't support the scoped lifestyle in its Dependency Injection module--it only supports transient and singleton. It'd be interesting to see the techniques here implemented in that module with a Lifestyle.Scoped attribute.

This isn't a perfect solution:

  1. It's still possible you could accidentally inject IScopedService instead of Func<IScopedService> into a Configuration Factory type and have some runtime exceptions in production.
  2. Although the above extension methods reduce the ceremony of registering scoped services, it's still possible to accidentally register your scoped services with AddScoped and have a bad time. Though some containers, like Autofac, do this Func<T> registration for you automatically, if you're using them in your solution.
  3. Injection and registration of Func<T> isn't the most readable code--it will likely be confusing for devs who haven't seen this pattern before.

However, until Sitecore fixes this in the Configuration Factory, it's the best way to do dependency injection of scoped services with the Configuration Factory. And it's the only way to inject services of shorter lifetimes into services of longer lifetimes.