DIP is a design principle that promotes loosely coupled software architecture. It states that: High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions. In simpler terms: Instead of tightly coupling your classes by having them depend on concrete implementations, they should depend on abstractions (interfaces or abstract classes). This allows you to easily swap out implementations without changing the higher-level code.
Inversion of Control (IoC): Shifting Responsibility IoC is a broad principle that involves transferring the control of object creation and management from your application code to a framework or container. Instead of your classes explicitly creating their dependencies, they receive them from an external source. This external source is often a DI container.
Dependency Injection (DI): The Practical Tool DI is a specific implementation of the IoC principle. It involves supplying dependencies to a class from outside the class itself. The most common way to do this in ASP.NET Core is through constructor injection, but there are also other techniques like property injection and method injection.
Benefits of DIP, IoC, and DI Loose Coupling: Reduces the direct dependencies between classes, making them easier to change and test independently. Flexibility: You can easily swap out different implementations of dependencies without affecting the consuming class. Testability: Unit testing becomes much easier, as you can provide mock dependencies to isolate the code under test. Maintainability: Code becomes more modular, easier to understand, and less prone to ripple effects from changes.
Code // ServiceContracts (Interface) namespace ServiceContracts { public interface ICitiesService // Abstraction of CitiesService { List<string> GetCities(); } }
// Services (Implementation) namespace Services { public class CitiesService : ICitiesService // CitiesService depends on the ICitiesService abstraction { // … (Implementation of GetCities) … } }
The interface ICitiesService defines the abstraction for a service that can retrieve a list of cities. The class CitiesService provides the concrete implementation, but it depends on the ICitiesService interface, not on a concrete class.
Code // Program.cs (or Startup.cs) builder.Services.Add(new ServiceDescriptor( typeof(ICitiesService), // Interface to register typeof(CitiesService), // Concrete implementation ServiceLifetime.Transient // Lifetime of the service (more on this later) ));
// HomeController.cs (Controller) public class HomeController : Controller { private readonly ICitiesService _citiesService; // Dependency on the interface
In this code: Service Registration: The CitiesService is registered in the DI container using the Add method. The ServiceDescriptor specifies: The interface type (ICitiesService) that other components will request. The concrete implementation type (CitiesService) that the container will create. The lifetime of the service (ServiceLifetime.Transient means a new instance is created for each request). Constructor Injection: The HomeController constructor has a parameter of type ICitiesService. This means the DI container will automatically provide an instance of CitiesService when the controller is created.