In this post, we will do some exercises to go over the basics of DI (Dependency Injection) in ASP.NET.
What's the result of the following code?
public interface IServiceA { }
class ServiceA : IServiceA
{
ServiceA()
{
Console.WriteLine("New SA");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IServiceA, ServiceA>();
...
}
}
It will raise an exception:
System.AggregateException: 'Some services are not able to be constructed'
A suitable constructor for type 'AspNetCore.Services.ServiceA' could not be located.
Ensure the type is concrete and services are registered for all parameters of a public constructor.
As mentioned in ASP.NET Core doc, constructor injection requires a public constructor.
Why public constructor? Because the default accessibility level of constructor is private. DI is implemented by ASP.NET Core, it cannot access other levels except public.
Why not public class? Because the DI framework already gets the class by method call. If we want to access a class by using namespace, public is needed.
When the following app is running without any request coming, will the singleton service be initialized?
public interface IServiceA { }
public class ServiceA : IServiceA
{
public ServiceA()
{
Console.WriteLine("New SA");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServiceA, ServiceA>();
...
}
}
public class HelloController : ControllerBase
{
public WeatherForecastController(IServiceA sa)
{
Console.WriteLine($"Test Controller: {sa.GetHashCode()}");
}
}
The ServiceA won't be initialized. The DI framework,m will check the constructor (I guess), but won't initialize the instance. It is only when the singleton service is used that it will be initialized.
In this case, the service is depended by the controller, and the controller will be initialized for every request. So, as a singleton service, the ServiceA will be initialized when the first request comes. When other requests come after the first request, the singleton service won't be initialized any more:
// 1st request
New SA
Test Controller: 83452835
// 2nd request
Test Controller: 83452835
// 3rd request
Test Controller: 83452835
If we use `AddScoped` instead of `AddSingleton`, the DI framework will initialize a new instance for every request.
// 1st request
New SA
Test Controller: 72334852
// 2nd request
New SA
Test Controller: 83729442
// 3rd request
New SA
Test Controller: 19231424
If we inject dependencies in order of A-B, and fetch them in order of B-A, which one will be initialized first?
public interface IServiceA { }
public class ServiceA : IServiceA
{
public ServiceA()
{
Console.WriteLine("New SA");
}
}
public interface IServiceB { }
public class ServiceB : IServiceB
{
public ServiceB()
{
Console.WriteLine("New SB");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServiceA, ServiceA>();
services.AddSingleton<IServiceB, ServiceB>();
...
}
}
public class HelloController : ControllerBase
{
public WeatherForecastController(IServiceB sb, IServiceA sa)
{
Console.WriteLine($"Test Controller: {sa.GetHashCode()}");
}
}
Result:
New SB
New SA
Test Controller: 83427434 22834295
Although the injection order is A-B, but B will be first initialized because we fetch B first.
What if B depends on A?
public class ServiceB : IServiceB
{
public ServiceB(IServiceA sa)
{
Console.WriteLine($"New SB with sa: {sa.GetHashCode()}");
}
}
Result:
New SA
New SB with sa: 46284926
Test Controller: 46284926 64753745
ServiceA will be initialized first, then ServiceB. Given ServiceA is singleton, it will not be initialized again.
What if we inject B before A, but B depends on A?
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IServiceB, ServiceB>();
services.AddScoped<IServiceA, ServiceA>();
}
Result:
New SA
New SB with sa: 72362183
Test Controller: 72362183 91218567
The order of injection doesn't matter. The DI container will keep the relationship between the interface and the implement class, and will properly handle everything for us at initialization time.
If there are multiple implement class and they are all injected, which one will be initialized?
public interface IServiceA { }
public class ServiceA : IServiceA
{
public ServiceA()
{
Console.WriteLine("New SA");
}
}
public class ServiceB : IServiceA
{
public ServiceB()
{
Console.WriteLine("New SB");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServiceA, ServiceA>();
services.AddSingleton<IServiceA, ServiceB>();
...
}
}
public class HelloController : ControllerBase
{
public WeatherForecastController(IServiceA sa)
{
Console.WriteLine($"Test Controller: {sa.GetHashCode()}");
}
}
Result:
New SB
Test Controller: 46399782
Only the last one will be initialized. If you want the first one, use TryAdd. TryAdd won't work if there already exists a registration for the given service interface.
If there is multiple interfaces and only one implement class registered as singleton, how many times will it be initialized?
public interface IServiceA { }
public interface IServiceB { }
public class ServiceB : IServiceA, IServiceB
{
public ServiceB()
{
Console.WriteLine("New SB");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServiceA, ServiceB>();
services.AddSingleton<IServiceB, ServiceB>();
...
}
}
public class HelloController : ControllerBase
{
public WeatherForecastController(IServiceA sa, IServiceB sb)
{
Console.WriteLine($"Test Controller: {sa.GetHashCode()} {sb.GetHashCode()}");
}
}
Result:
New SB
New SB
Test Controller: 78346274
As you see, the singleton in `AddSingleton` refers to the interface not the implement. ServicesB is the implement class of two interfaces, they will be initialized separately.
All these exercises are very basic and common. Some parts are just my personal opinion. I am a ASP.NET Core beginner, please correct my if you think anything is wrong.
If you want to learn more about DI in ASP.NET Core, source code of Microsoft.Extensions.DependencyInjection is a good reading.