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.