Using Scoped dependencies in Singletons in ASP.NET Core

...or any dependencies with a smaller scope than a given component to that matter

Many of those who know me may be surprised that I've written an article related to M$ technologies: I know, I know: it's a bit like if Osama converted to Christianity or something ;) Well, I'm leading a project consisting of several applications written in many different technologies and one of them happens to be ASP.NET Core and while I was trying to understand what my ASP.NET sub-team was doing I found some areas for improvement ;)

Let's start from explaining what the problem is by giving a simple example: let's say I have a singleton component that uses some other component scoped to the current HTTP request (for example a Repository/DAO that uses entity framework). If I simply inject such a dependency in a constructor of my singleton the things will work ok only during the 1st request. During any subsequent request my singleton will be still holding a reference to a Repository/DAO instance injected to him while processing the 1st request and it won't work as a separate instance for each request should be used.

In Java world there's a concept of Provider:

interface Provider<T> {
	T get();
}
Generally providers are objects containing logic that decides to whom a call to get a new instance of a service should be delegated. Moreover, in most Java DI frameworks when a binding for some type T is registered then we can inject an automatically generated instance of Provider<T> instead of T and later get an instance of T from it whenever it is needed. This among other things helps to solve the scoping issue: an automatically generated provider makes sure that we get an instance appropriate for the current scope (whatever this scope might be: HTTP request, HTTP session or any other custom scope).
When developing component classes one should generally assume as little as possible about their dependencies (abstraction rule), including what the scopes of these dependencies are. Therefore I myself actually almost always tend to inject instances of Provider of my dependencies instead of instances of the dependencies directly.
My ASP team did not tend to follow this practice, so I explained to Quốc, my ASP team leader, the issue of using dependencies with a smaller scope than a given component and asked to use a similar mechanism. To my surprise he said that to his knowledge there was nothing like that available in the DI framework built into ASP.NET Core. I've read this article regarding DI in ASP which wisely warns about the dangers associated with injecting a dependency with a smaller scope, but unfortunately does not explain how to solve this issue.
"Ok, no problem" I thought: since C# generics don't suck like Java's do, it should be just few lines of code to create providers in C#, so we've created with Quốc the following interface and class:
public interface IProvider<T> {
	T Get();
}

public class Provider<T>: IProvider<T> {

	IServiceProvider serviceProvider;

	public Provider(IServiceProvider serviceProvider) {
		this.serviceProvider = serviceProvider;
	}

	public T IProvider<T>.Get() {
		return serviceProvider.GetService<T>();
	}
}
and attempted to use it the standard way:
public class SingletonService : ISingletonService {

	IProvider<IScopedService> scopedServiceProvider;

	public SingletonService(IProvider<IScopedService> scopedServiceProvider) {
		this.scopedServiceProvider = scopedServiceProvider;
	}

	public string doSomething() {
		var scopedDependency = scopedServiceProvider.Get();
		// do something with scopedDependency to verify we get instances
		// appropriate for the current scope
	}
}
We configured it in our Startup class:
public void ConfigureServices(IServiceCollection services) {
	services.AddSingleton<ISingletonService, SingletonService>();
	services.AddScoped<IScopedService, ScopedService>();
	services.AddTransient<IProvider<IScopedService>, Provider<IScopedService>>();
	// other bindings here
}
Unfortunately this didn't work the way we intended as IServiceProvider instance seems to be also scoped to the current HTTP request and we were getting exactly the same instance of ScopedService from our scopedServiceProvider during processing of different requests :(
I asked Quốc if there was any "higher level" object than ServiceProvider maybe, bound roughly to application lifecycle (not to the current HTTP request) that created instances of request scoped objects (or of ServiceProvider itself), so that we could inject this "higher level" object into our Provider instances instead of IServiceProvider. For example in Guice (Java DI framework) there is an Injector object, usually created at the startup of an application which holds all the type bindings and has a method <T> T getInstance(Class<T> type) which checks what is the current scope and returns a corresponding instance.
He said that he was not aware of anything like that, but suggested that we could try to obtain a "valid" instance of IServiceProvider directly from an HttpContext. Injecting an instance of IHttpContext into Provider would end up the same way as injecting IServiceProvider of course, so we needed to obtain it each time in the Proivder<T>.Get() method:
public class Provider<T> : IProvider<T> {

	IHttpContextAccessor contextAccessor;

	public Provider(IHttpContextAccessor contextAccessor) {
		this.contextAccessor = contextAccessor;
	}

	T IProvider<T>.Get() {
		return contextAccessor.HttpContext.RequestServices.GetService<T>();
	}
}
and the configuration:
public void ConfigureServices(IServiceCollection services) {
	services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
	services.AddSingleton<ISingletonService, SingletonService>();
	services.AddScoped<IScopedService, ScopedService>();
	services.AddTransient<IProvider<IScopedService>, Provider<IScopedService>>();
	// other bindings
}
This has done the trick :)
There's some more detailed information about binding IHttpContextAccessor here.

Note:

Unlike Java Servlet instances, ASP Controller instances are created per each HTTP request, so situations of using a dependency with a smaller scope than a given component are rare. In Java, Servlet objects are by default singletons, so Java engineers face this problem "na dzień dobry" (Polish language expression meaning "from the very beginning").



Copyright 2016 Piotr Morgwai Kotarbiński