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.
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.
interface IGetCreatedTime
{
DateTime GetCreatedTime();
}
interface ISingletonGetCreatedTime : IGetCreatedTime
{
}
interface IScopedGetCreatedTime : IGetCreatedTime
{
}
interface ITransientGetCreatedTime : IGetCreatedTime
{
}
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:
public class GetCreatedTimeImplementation : ITransientGetCreatedTime,
IScopedGetCreatedTime,
ISingletonGetCreatedTime
{
private readonly DateTime createdOn;
public GetCreatedTimeImplementation()
{
this.createdOn = DateTime.Now;
}
public DateTime GetCreatedTime()
{
return this.createdOn;
}
}
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.
class GetCreatedTimeInvoker
{
private readonly ISingletonGetCreatedTime singleton;
private readonly IScopedGetCreatedTime scoped;
private readonly ITransientGetCreatedTime transient;
public GetCreatedTimeInvoker(ISingletonGetCreatedTime singleton,
IScopedGetCreatedTime scoped, ITransientGetCreatedTime transient)
{
this.singleton = singleton;
this.scoped = scoped;
this.transient = transient;
}
public void Invoke()
{
Console.WriteLine($"Singleton Response: {singleton.GetCreatedTime():MM/dd/yyyy hh:mm:ss.fff tt}, Stays the same.");
Console.WriteLine($"Scoped Response: {scoped.GetCreatedTime():MM/dd/yyyy hh:mm:ss.fff tt}, Changes only if scope is changed");
Console.WriteLine($"Transient Response: {transient.GetCreatedTime():MM/dd/yyyy hh:mm:ss.fff tt}, Changes everytime this method is invoked");
}
}
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.
class Program
{
static async Task Main(string[] args)
{
using IHost host = CreateHostBuilder(args).Build();
ExecuteScope(host.Services, "sample-scope-1");
ExecuteScope(host.Services, "sample-scope-2");
await host.RunAsync();
}
static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
services.AddTransient<ITransientGetCreatedTime, GetCreatedTimeImplementation>()
.AddScoped<IScopedGetCreatedTime, GetCreatedTimeImplementation>()
.AddSingleton<ISingletonGetCreatedTime, GetCreatedTimeImplementation>()
.AddTransient<GetCreatedTimeInvoker>());
static void ExecuteScope(IServiceProvider services, string scope)
{
using IServiceScope serviceScope = services.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
Console.WriteLine("===================================================");
Console.WriteLine($"Scope Name: {scope}");
Console.WriteLine("===================================================");
Console.WriteLine($"First Call....");
GetCreatedTimeInvoker invoker = provider.GetRequiredService<GetCreatedTimeInvoker>();
invoker.Invoke();
Console.WriteLine("...");
Console.WriteLine($"Second Call....");
invoker = provider.GetRequiredService<GetCreatedTimeInvoker>();
invoker.Invoke();
Console.WriteLine("===================================================");
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
}
}
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.