
Introducing Dependency Injection in .NET
Author - Abdul Rahman (Bhai)
Dependency Injection
7 Articles
Table of Contents
What we gonna do?
In this article, let's learn about Dependency Injection in .NET.
.NET supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. Dependency injection in .NET is a built-in part of the framework.
Why we gonna do?
A dependency is an object that another object depends on. Examine the following ClassB class with that other classes depend on:
public class ClassB
{
// omitted for brevity
}
A class can create an instance of the ClassB class to make use of it. In the following example, the ClassB class is a dependency of the ClassA class:
public class ClassA
{
private readonly ClassB _classB = new();
public void Run()
{
// omitted for brevity
}
}
The class creates and directly depends on the ClassB class. Hard-coded dependencies, such as in the previous example, are problematic and should be avoided for the following reasons:
- To replace ClassB with a different implementation, the ClassA class must be modified.
- If ClassB has dependencies, they must also be configured by the ClassA class. In a large project with multiple classes depending on ClassB, the configuration code becomes scattered across the app.
- This implementation is difficult to unit test. The app should use a mock or stub ClassB class, which isn't possible with this approach.
So let's try to get the instance of ClassB from constructor of ClassA.
public class ClassA
{
private readonly ClassB _classB;
ClassA(ClassB classB)
{
_classB = classB;
}
public void Run()
{
// omitted for brevity
}
}
Here is the complete wire up in Program.cs.
class Program
{
var classB = new ClassB();
var classA = new ClassA(classB);
classA.Run();
}
Dependency injection addresses these problems through:
- The use of an interface or base class to abstract the dependency implementation.
- Registration of the dependency in a service container. .NET provides a built-in service container, IServiceProvider. Services are typically registered at the app's start-up and appended to an IServiceCollection. Once all services are added, you use BuildServiceProvider to create the service container.
- Injection of the service into the constructor of the class where it's used. The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.
class Program
{
using var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddTransient<ClassB>();
services.AddTransient<ClassA>();
})
.Build();
var classA = host.Services.GetRequiredService<ClassA>();
classA.Run();
}
How we gonna do?
Dependency Injection visualization
DI enables decoupling and supports development of loosely-coupled code. DI supports implementation of two related concepts, inversion of control and the dependency inversion principle.
- Inversion of Control
- - A framework controls which code is executed next, not your code.
- Dependency Inversion Principle
- - High-level modules should not depend on low-level modules. Both should depend on abstractions.

Working with Dependency Injection
DI Configuration consist of two main phases, Registration phase and Resolving Phase.
Steps
- Install Microsoft.Extensions.Hosting Nuget Package.
Register services using
var host = Host.CreateDefaultBuilder(args) .ConfigureServices((context, services) => { //Registration goes here }) .Build();- Resolve services using Host.Services.GetRequiredService<YourService>();

Phases of Dependency Injection
Registration Phase
Register types in container so it knows of their existence and when to construct them.
- Register types for later use.
- Indirection through service type and implementing type.
- Choose a lifetime.
Registering Types
When registering types you specify the lifetime, the requested service type and the implementing type. If these types are the same you provide it once.
services.AddTransient<IService, MyService>();
services.AddSingleton<ClassA>();
Resolving Phase
Container is responsible for instantiating types and providing them when requested.
- Resolves and creates types directly.
- Provides dependencies of types you work with.
- Provides dependencies to dependencies of the types you work with.
- Manage the lifetimes of the types.
Resolving Types
When resolving a type, you request an instance of service type. The container will find the implementing type, instantiate it if needed, and return it to you.
If the implementing type has dependencies, they are provided to the implementing type as well.
host.Services.GetRequiredService<IService>();
host.Services.GetRequiredService<ClassA>();

Advantages
- No longer calling constructors.
- Container calls the constructors.
- Not concerned with ordering registrations.
- Not concerned with dependencies of each type.
Summary
In this article, we learn't what is Dependency Injection in .NET. We understood how classes are invoked in traditional flow using new() keyword and how dependency injection helped us to avoid the new() glue and makes it easy to manage dependencies. In next article lets talk about lifetimes in dependency injection in .NET.