ASP.NET Core provides a built-in Dependency Injection (DI) Container, also known as the Inversion Of Control (IoC) Container.
The DI Container is responsible for managing dependencies and providing instances of required services. It follows the Dependency Inversion Principle by working with abstractions instead of concrete implementations.
Services are registered using the IServiceCollection interface. The following image adds the services for controllers in a Web API:
At runtime, we can get a service instance with the IServiceProvider interface. In this case, we create a new ServiceProvider class and use the GetService method from the IServiceProvider to get an IDependency service instance:
Dependencies are managed during a determined lifetime. The DI Container controls how long a service lasts. When the lifetime is over, the dependency is automatically released to the garbage collector by using the IDispose interface.
There are three types of Service Lifetimes:
Transient
A new instance is created every time it is requested. A unique object is provided whenever a dependent class injects the service.
It´s the default choice because we can skip multi-threading scenarios, mutability and memory leaks. Transient services are suitable for lightweight, stateless applications that are thrown away frequently and have small performance costs for object creation.
Scoped
A new instance is created on each request (or scope). The service object is shared within that request.
It's recommended for web applications because it aligns with the HTTP request-response lifecycle, where 'request' objects can be reused throughout the application. Scoped services are thread-safe within the scope of a request because multiple requests can be processed without interference among them.
One example of a scoped service is the Entity Framework DbContext, which tracks changes within a single request to perform database operations.
Singleton
A single instance across the application. The dependency is created once and shared in all dependent classes.
It´s a good option for components that can be reused during the entire lifetime instance, such as configuration settings and logging or caching functionalities. It´s the more performant choice regarding memory efficiency but can cause memory leaks and multi-threading issues.
Note: The Captive Dependency term is applicable when working with interrelated dependencies. It's based on the fact that: "A service should never depend on a service that has a shorter lifetime than its own." Captive Dependency with ASP.NET Core | by Ashish Patel | .NET Hub | Medium
This safe dependencies approach is enabled by default in ASP.NET Core development mode. However, It's disabled in production mode.
BENEFITS
Loosely coupled code: Components are not bound to particular implementations. They use abstractions to handle dependencies using the Dependency Inversion Principle.
Highly testable system: Real dependencies can be substituted with mock objects.
Flexibility: A dependent service implementation is easily swapped without modifying the dependent code; it's based on the Open/Closed Principle.
Separation of Concerns: The DI Container is responsible for managing dependencies, allowing the Single Responsibility Principle to be applied in the dependent classes.
DRAWBACKS
DI Container misuse: Exposing the DI container´s IServiceProvider directly in the components makes testing and identifying class dependencies harder.
Limited advance features: ASP.NET Core Built-in DI Container is more limited than third-party DI Containers such as Autofac or Unity.
Complexity: Applying DI to small or straightforward systems can be an unnecessary overhead that can be solved by manually injecting dependencies.
Checkout my video:
Comments