DEV Community

Cover image for Exercises on Dependency Injection in  ASP.NET - Basics
Haiyang Wang
Haiyang Wang

Posted on • Updated on

Exercises on Dependency Injection in ASP.NET - Basics

In this post, we will do some exercises to go over the basics of DI(Dependency Injection) in ASP.NET.

Accessibility Levels

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.

Life Cycle

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 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

Initialization Order

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. And because 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.

One Interface & Multiple Implement

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.

Multiple Interface & One Implement

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.

Follow Up

All these exercises are very basic and common. Some parts are just my personal opnion. 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.


References:

Top comments (0)