Dependency Injection in C#
Introduction
I often receive questions about Dependency Injection (DI). So, I decided to pen down this blog to shed light on the topic and clarify its significance. Dependency Injection is a design pattern that promotes the SOLID principles, especially the Dependency Inversion Principle. By decoupling classes and their dependencies, DI encourages a more modular and testable codebase. It allows for greater flexibility, as changes in one part of the system don't ripple through the entire application.
Advantages of DI
Using Dependency Injection brings many benefits:
- Removes hardcoded dependencies, enabling changeability.
- Facilitates writing loosely coupled code, enhancing maintainability and extensibility.
- Increases testability as loosely coupled code can be tested more easily.
- Enables parallel development since implementations are independent.
- Improves code readability and cleanliness.
Service Lifetimes
When adding services to a container, you need to set their 'lifetime', determining how long an object exists after its creation:
- Transient: A new object for every request.
- Scoped: One object per request.
- Singleton: One object for the app's entire lifespan.
The DI container ensures these objects are properly managed based on their lifetime.
Example of Dependency Injection
To illustrate DI, consider the StudentModel and imagine an IStudentRepository and its implementation:
public interface IStudentRepository
{
StudentModel GetStudent(int id);
}
public class StudentRepository : IStudentRepository
{
public StudentModel GetStudent(int id)
{
// Logic to fetch student details
}
}
static void Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddScoped<IStudentRepository, StudentRepository>()
.BuildServiceProvider();
var studentRepo = serviceProvider.GetService<IStudentRepository>();
var student = studentRepo.GetStudent(1);
Console.WriteLine($"Student Name: {student.FirstName} {student.LastName}");
}
The code to be written in a more verbose or expanded manner, rather than using chained methods. This code does the exact same thing as the code in the main but is spread out for clarity.
// Create a new instance of ServiceCollection
ServiceCollection serviceCollection = new ServiceCollection();
// Register the service with a scoped lifetime
serviceCollection.AddScoped<IStudentRepository, StudentRepository>();
// Build the service provider from the service collection
IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
By focusing on the abstraction (IStudentRepository), our code is not tightly bound to the concrete StudentRepository class. This modular approach aids in the easy alteration of StudentRepository without impacting other parts of the system.