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).Provider
of my dependencies instead of instances of the dependencies directly.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 :(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.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 :)
IHttpContextAccessor
here.
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