How to Setup Demo Console Application to Use Dependency Injection – Windows ASP.NET Core Hosting 2024 | Review and Comparison

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.