Generally, if we create a .NET core web application using Visual Studio or dotnet CLI, the template already has Startup class. This class contains all basic setup, ready to use for using many different services.
So, in this article, let’s try to setup a demo console application to use dependency injection. That might help us understand the internals of how does it work.
Console App
Create a console application (let’s say DIConsoleDemo) using dotnet CLI or using the visual studio. Then add reference of the Microsoft.Extensions.Hosting NuGet package to the project.
Set of Interfaces
We are going to create an interface IGetCreatedTime
, which has a method GetCreatedTime
. The idea is to have a class which implements this interface (indirectly, wait for next step) and returns a DateTime
object, representing the time at which the current object was created.
To better understand the concept of singleton, scoped and transient service lifetimes discussed in previous post, let’s create three more interfaces:
ISingletonGetCreatedTime
IScopedGetCreatedTime
ITransientGetCreatedTime
Each one of the above three interfaces should implement the original interface IGetCreatedTime
and they will not contain any extra methods. All these 4 interfaces are shown in the below code snippet.
Implementation
The implementation class is simple. It will implement three interfaces corresponding to three lifetimes. The class will capture the current date time in the constructor and will hold it in the readonly
field. The GetCreatedTime
method from the interface should return the readonly
field.
The concrete class is shown in the code snippet below:
Invoker
This is a class where the constructor expects the three objects:
- first implementing
ISingletonGetCreatedTime
, - second implementing
IScopedGetCreatedTime
, - third one implementing
ITransientGetCreatedTime
Then there is a method invoke, which just invokes GetCreatedTime
method on each of the objects and prints the output as shown in below snippet.
Main
For dependency injection, there are three important interfaces:
- IHost, this is the class which exposes the property of
IServiceProvider
- IServiceCollection, the collection where we can register details about service interface, its concrete implementation and the lifetime of the service, typically while the application is bootstrapping.
- IServiceProvider, which acts as container responsible for creating/maintaining/disposing the services registered using IServiceCollection
This is where the Nuget package is going to help. The CreateDefaultBuilder method creates an IHostBuilder instance with the default binder settings. ConfigureServices method provides the instance of IServiceCollection (services parameter) which we have used to register three interfaces. So each of the interface creates object of same class, but the lifetime of the object created is different.
As this is console application, it is not like web application and that’s why there is always going to be a single scope. But to simulate multiple scopes we have added a call to IServiceProvider.CreateScope() which creates a new scope.
For every scope, we invoke the invoker object’s method two times and the creation time of every object is printed to help us understand how the objects are getting used.
Run and Verify
Run the application and you should be able to see the output as shown below:
So, we have successfully demonstrated how the service lifetime works. I hope you find this demo useful. Let me know your thoughts.
Pingback: The Code Blogger - Dependency Injection In .NET – Default Container vs Autofac
Thanks for the straightforward explanation. How would we set up some unit tests for this application?
Glad to know it helped you ! My question is – what exactly do you want to test ?
If you want to test the business functionality (equivalent to GetCreatedTimeInvoker in this post), then you can just instantiate it using new operator inside the test.
If you want to test the DI container setup itself, then you must refer the same method which initializes the container to setup a container in the unit test. Then resolve the services explicitly to ensure that they are getting resolved as expedted.
Hope this helps !
Hi Manoj, I’m wanting to test the business functionality but will need to load config – eg db connections. So just calling new on the business object wont work as it would have no config info.
If I understand correctly, you want to use your configurations to connect to database. If you need to test functionality which includes database connectivity, I would suggest to better go for API / Integration tests. Integration tests will not have any mocks and you will need to use the same startup class from your host application in the tests. If you just need to test the functionality, without needing to connect to actual database, then you can write unit tests. In case of unit tests, you will have to mock the dependencies while instantiating the business implementation. Hope this helps.