It is impossible nowadays to design classes/controllers which don’t depend on other objects. Rather than instantiating those objects inside other objects directly, it is a good practice to register them once and then use them as needed. This improves testability and maintenance due to loose coupling. You would realize this benefit when you write mock tests and have different implementations. In this article, we will see three different ways you can inject dependencies and also look into pitfalls to avoid memory leaks.
Using Constructor Injection
This is the most common way of resolving dependencies. Below is an interface to implement a custom logger.
Now let’s create a concrete class that implements the above interface.
Register above class in Program.cs file as a scoped service.
And then use it in your controller as shown below.
The DefaultController constructor accepts an instance of type ICustomFileLoger as a parameter. The concrete object is provided by .NET IoC (Inversion of Control) container.
Ok great, but why register the dependency as a scoped service? Let’s take a look at three service lifetimes provided by .NET. By service lifetime, we intend to say when the service (object) would be disposed of.
Singleton
This lifetime creates only one instance of the service. This service will be alive in the memory as long as your application running on the server. Once you bring down your application, the object wouldn’t be accessible anymore when you restart the application. Such lifetime is great for use cases like caching.
Although caching is a good candidate for this lifetime, you should create a mechanism to update/invalidate the cached data when the original data source changes.
One case which might discourage you to use this lifetime is memory leak. The objects are not released/disposed of until the end of the application. Thus, it is important to dispose them off at the right time. Also, if you use scoped or transient services from singleton services it might create a problem because transient services are not designed to be thread-safe. If you have no other choice and still need to use them, then take care of multi-threading.
You can create a singleton service using the Add() method or AddSingleton() method.
Transient
This lifetime creates objects each time you request it. This means, that a service injected in the constructor of a class will last as long as that class instance exists. Use this lifetime when you generally don’t care about multi-threading and memory leaks as the service has a short life. As discussed in the previous lifetime, do not use it from singleton lifetime as it will get converted into singleton when you expect it to be transient.
To create a service with a transient lifetime, you have to use the AddTransient() method.
Scoped
The scoped lifetime allows you to create an instance of a service for each client (connection) request. This is a great lifetime as all the services will use this single instance during the same client request. The good point of this lifetime is that you don’t need to worry about thread safety as each request will have its own instance.
Use AddScoped() method to create this lifetime service.
Using Action Method Injection
You should always prefer constructor injection whenever you need to use the injected instance in multiple methods of the same class. However, if that is not the case and you just need the service instance in only one method, you can ask IoC container from the method itself.
Using IServiceProvider
There are cases where you need multiple services to be injected into your class. In our case, it is just customFileLogger but you might need some more like repository and domain services.
In such a case, you can just inject IServiceProvider into constructor and then use it to request a specific instance of the service.
Though this approach looks good, Microsoft recommends not to use it. Instead, use Constructor Injection pattern.
Conclusion
The beauty of DI is that you register your service and rely on the platform to create instances and dispose them off for you. It is not mandatory to register all services. There are two types of services in .NET.
Framework services: these are part of the .NET Core framework. For example, IApplicationBuilder, IConfiguration, ILoggerFactory, etc.
Application services: these are the ones that you create in your application since IoC doesn’t know them, you need to register them explicitly.
Let me also brief you on some terms that you might come across when reading about dependency injection.
Dependency Inversion Principle
It is a software design principle; it suggests a solution to the dependency problem but does not say how to implement it or which technique to use it.
Inversion of Control
It is a way to implement the Dependency Inversion Principle. Rather than you controlling object creation and deletion, the control is with the framework. So the control is inversed.
Dependency Injection
It is a design pattern to implement Inversion of Control. Through this, you can inject a concrete implementation of a low-level component into a high-level component.
IoC Container
A programming framework that provides you with an automatic Dependency Injection of your components.
Source: Azilen Technologies
The Tech Platform
Comments