Exercises on Dependency Injection in  ASP.NET - Basics
Tags: dotnet
Published: 2020-06-25 06:31:37

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?

```csharp
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](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#constructor-injection-behavior), 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?

```csharp
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?


```csharp
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?

```csharp
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?

```csharp
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?

```csharp
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?

```csharp
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](https://github.com/dotnet/runtime/tree/master/src/libraries/Microsoft.Extensions.DependencyInjection) is a good reading.


***
References:
- [Microsoft.Extensions.DependencyInjection Source Code](https://github.com/dotnet/runtime/tree/master/src/libraries/Microsoft.Extensions.DependencyInjection) 
- [Dependency Injection | ASP.NET Core Docs](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1)
- [Understanding Dependency Injection in .NET Core](https://auth0.com/blog/dependency-injection-in-dotnet-core/)
- [ASP.NET Core Dependency Injection Deep Dive](https://joonasw.net/view/aspnet-core-di-deep-dive)
- [ASP.NET Core Dependency Injection](https://volosoft.com/blog/ASP.NET-Core-Dependency-Injection)
- [.Net Core Dependency Injection](https://stackify.com/net-core-dependency-injection/)
- [How to use dependency injection in ASP.Net Core](https://www.infoworld.com/article/3232636/how-to-use-dependency-injection-in-aspnet-core.html)