In the world of software development, Dependency Injection (DI) stands as a fundamental concept, often hailed as a cornerstone of good design and modular architecture. But, for many developers, especially those newer to the field, the term can evoke confusion or difficult to understand. I hope that the blog will help you understand DI better.

1. Understanding Dependency Injection

At its core, Dependency Injection is a design pattern used to manage dependencies between different components or modules within a software system. It can be understood simply as follows:
  • Modules do not communicate directly with each other, but through an interface. The low-level module will implement the interface, the high-level module will call the low-level module through the interface.
  • For example: To communicate with customer service, we have the ICustomerService interface, the low-level modules are CustomerService. The high-level module CustomerController will only use the ICustomerService interface.
  • Initialization of low-level modules will be performed by DI Container. For example: In the CustomerController, we will not initialize ICustomerService service = new CustomerService (), this will be done by DI Container. The CustomerController will not know anything about the class CustomerService
  • Which Module is attached to which interface will be configured in the class Program.cs

2. The Three Types of Dependency Injection

Dependency Injection can be implemented in three main ways: Constructor Injection, Setter Injection, and Interface Injection.
  • Constructor Injection: In this approach, dependencies are provided through a class's constructor. This ensures that all required dependencies are available when an object is instantiated, promoting immutability and simplifying testing.
  • Setter Injection: Also known as property injection, Setter Injection involves providing dependencies through setter methods. While not as preferred as Constructor Injection due to the potential for objects to be in an invalid state, Setter Injection can be useful for optional dependencies.
  • Interface Injection: This approach is less common and involves providing dependencies through an interface that the client class implements. However, this method can introduce tight coupling between the client class and the injector, making it less flexible compared to Constructor Injection.
Among the three types of Inject, Constructor Injection method is very popular because of its flexibility, ease of building DI libraries...

3. Advantages and disadvantages of Dependency Injection

Advantage

  • Reduce adhesion between modules
  • Code is easy to maintain, easy to replace modules
  • Very easy to test and write Unit Test

Disadvantages

  • The concept of DI is quite difficult to understand new developers will have difficulty learning it
  • Objects are completely initialized from the beginning, which can reduce performance Increases code complexity

4. Using DI in .NET CORE

Use Dependency Injection through these steps:
  1. Use an interface or base class to abstract implementation dependencies.
  2. Register the dependency in the service container. ASP.NET Core allows us to register our application services with the IoC container, in the Program.cs class use IServiceCollection to register application services
  3. Include the service in the constructor of the class in which it is used. The framework will create an instance of the dependency and remove it when it is no longer needed.
Example: The ICustomerService interface defines the SendMessage method
public interface ICustomerService {
    void SendMessage(string message);
}
We have the CustomerAService implement ICustomerService1
public class CustomerAService : ICustomerService {
    public void SendMessage(string message)
    {
        Console.WriteLine($"CustomerAService.SendMessage Message: {message}");
    }
}
The AddScoped method registers the service with scoped lifetime, the lifetime of a singleton request
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<ICustomerAService, CustomerAService>();
 
var app = builder.Build();
There are 3 lifecycle levels: addTransient, addScoped, addSingleton.
  • Transient: Instance is initialized each time a service is created
  • Scoped: Instance is initialized per scope. (Scope here is each request sent to the application). In the same scope, the service will be reused.
  • Singleton: The service instance is created uniquely at application launch and is used everywhere
ICustomerService is required and used to call the SendMesasge method
public class CustomerController : PageModel {
    private readonly ICustomerService _customerService;
    public Index2Model(ICustomerService customerService) {
        _customerService = customerService;
    }
    public void OnSendMesasge() {
        _customerService.SendMessage("Send message");
    }
}
By using the DI pattern, the controller will: Not use CustomerAService, only use the ICustomerService interface. That makes it easy to change the Controller's implementation without modifying the Controller.

Cover image from freepik.com

Leave a comment

*