Dependency injection: scoped classes
There are three ways to register classes for dependency injection: singleton, scoped and transient. A class registered as singleton will be created and returned the first time it is called for and then the same instance is returned every subsequent time it is called for. A class registered as transient will be created and returned every time it is called for.
What about scoped classes?
Scoped classes behave like singleton inside of a scope and as transient between scopes. In other words, a class registered as scoped is created and returned the first time it is called for in a scope and then the same instance is returned every subsequent time it is called for in that scope.
What is a scope?
A scope, for the purposes of dependency injection, is most often a HTTP request. Scoped classes is mostly used in backend development for the web. But it doesn't have to be, you can create a scope yourself. In that case a scope can be whatever you want it to be.
How?
Here is an example where three classes are registered as singleton, scoped and transient respectively. These three classes are used by two middle classes which are in turn used by one top class. Two scopes are then created and used to resolve the top class twice.
Console application
using Microsoft.Extensions.DependencyInjection;
interface ISingletonClass { static int Count = 0; }
interface IScopedClass { static int Count = 0; }
interface ITransientClass { static int Count = 0; }
interface IMiddleClassA { }
interface IMiddleClassB { }
interface ITopClass { }
class SingletonClass : ISingletonClass { public SingletonClass() { ISingletonClass.Count++; } }
class ScopedClass : IScopedClass { public ScopedClass() { IScopedClass.Count++; } }
class TransientClass : ITransientClass { public TransientClass() { ITransientClass.Count++; } }
class MiddleClassA(ISingletonClass a, IScopedClass b, ITransientClass c) : IMiddleClassA { }
class MiddleClassB(ISingletonClass a, IScopedClass b, ITransientClass c) : IMiddleClassB { }
class TopClass(IMiddleClassA a, IMiddleClassB b) : ITopClass { }
internal class Program
{
private static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddSingleton<ISingletonClass, SingletonClass>();
services.AddScoped<IScopedClass, ScopedClass>();
services.AddTransient<ITransientClass, TransientClass>();
services.AddTransient<IMiddleClassA, MiddleClassA>();
services.AddTransient<IMiddleClassB, MiddleClassB>();
services.AddTransient<ITopClass, TopClass>();
IServiceProvider provider = services.BuildServiceProvider();
using var scope1 = provider.CreateScope();
using var scope2 = provider.CreateScope();
scope1.ServiceProvider.GetRequiredService<ITopClass>();
scope2.ServiceProvider.GetRequiredService<ITopClass>();
Console.WriteLine("Singleton: " + ISingletonClass.Count);
Console.WriteLine("Scoped : " + IScopedClass.Count);
Console.WriteLine("Transient: " + ITransientClass.Count);
Console.ReadLine();
}
}
Take a moment to think about what counts the program will write at the end. Also, consider whether the counts will change if the middle and top classes were registered as singleton or scoped instead of transient.
Click the blurred text below to reveal the answers.