Dependency Injection in .NET Core Console Application

In this blog post, I will write about Dependency Injection in .NET Core Console Application. I will start from where I left in my last blog https://dotnetcorecentral.com/blog/docker-running-background-thread/.

dependency injection

What is Dependency Injection?

In Dependency Injection, dependencies of an object are sent from outside. Meaning, the dependencies of an object are not created inside of the class.

All the dependency injection packages support injecting dependencies through constructor of the class. And that’s the preferable way of using dependency. Some packages support dependency through a property as well. But I personally prefer constructor injection, as it prevents the object from being mutated.

Refactoring the code

Firstly, I will refactor the existing Main method to have the implementation across multiple classes. Each class having its own responsibility. Below is the implementation of the Main method:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Docker.Demo
{
    class Program
    {
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);

        static void Main(string[] args)
        {
            Console.WriteLine("Hello Docker World!");
            var count = 0;

            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Console.WriteLine($"Current Count {++count}");
                    Thread.Sleep(1000);
                }
            });

            Console.WriteLine("Press Ctrl + C to cancel!");
            Console.CancelKeyPress += ((s, a) =>
            {
                Console.WriteLine("Bye!");
                _closingEvent.Set();
            });

            _closingEvent.WaitOne();
        }
    }
}

Extracting the task

Firstly, I will extract the whole code into a new class named ContinuousRunningProcessor. The responsibility of the Program class will be just to instantiate ContinuousRunningProcessor and start it. But for the time being, we will keep Program class’s Main method to have only one Console.WriteLine method.

using System;

namespace Docker.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello Docker World!");
        }
    }
}
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Docker.Demo
{
    internal class ContinuousRunningProcessor
    {
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);

        public void Process()
        {
            var count = 0;

            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Console.WriteLine($"Current Count {++count}");
                    Thread.Sleep(1000);
                }
            });

            Console.WriteLine("Press Ctrl + C to cancel!");
            Console.CancelKeyPress += ((s, a) =>
            {
                Console.WriteLine("Bye!");
                _closingEvent.Set();
            });

            _closingEvent.WaitOne();
        }
    }
}

Refactoring ContinuousRunningProcessor

Secondly, I will refactor ContinuousRunningProcessor to give the responsibility of printing to ConsolePrinter.

using System;

namespace Docker.Demo
{
    internal interface IConsolePrinter
    {
        void Print(int count);
    }

    internal class ConsolePrinter : IConsolePrinter
    {
        public void Print(int count)
        {
            Console.WriteLine($"Current Count {count}");
        }
    }
}

Finally, I will introduce a class PrintSettingsProvider, whose responsibility is to provide a setting to ConsolePrinter to determine if it can print.

using System;
using System.Collections.Generic;
using System.Text;

namespace Docker.Demo
{
    internal interface IPrintSettingsProvider
    {
        bool CanPrint();
    }

    internal class PrintSettingsProvider : IPrintSettingsProvider
    {
        public bool CanPrint()
        {
            return true;
        }
    }
}
using System;

namespace Docker.Demo
{
    internal interface IConsolePrinter
    {
        void Print(int count);
    }

    internal class ConsolePrinter : IConsolePrinter
    {
        private readonly IPrintSettingsProvider printSettingsProvider;

        public ConsolePrinter(IPrintSettingsProvider printSettingsProvider)
        {
            this.printSettingsProvider = printSettingsProvider;
        }

        public void Print(int count)
        {
            if (printSettingsProvider.CanPrint())
            {
                Console.WriteLine($"Current Count {count}");
            }
        }
    }
}

The final version of ContinuousRunningProcessor

Moreover, now I will refactor ContinuousRunningProcessor to introduce IConsolePrinter to print. In addition, I will add a ILogger to log the messages. Now to do that I will add the NuGet package Microsoft.Extensions.Logging and Microsoft.Extensions.Logging.Console. The package Microsoft.Extensions.Logging.Console to log in the console.

logging nuget packages
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Docker.Demo
{
    internal class ContinuousRunningProcessor
    {
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);
        private readonly IConsolePrinter consolePrinter;
        private readonly ILogger<ContinuousRunningProcessor> logger;

        public ContinuousRunningProcessor(IConsolePrinter consolePrinter, ILogger<ContinuousRunningProcessor> logger)
        {
            this.consolePrinter = consolePrinter;
            this.logger = logger;
        }

        public void Process()
        {
            var count = 0;

            Task.Factory.StartNew(() =>
            {
                logger.LogInformation("Process started!");
                while (true)
                {
                    consolePrinter.Print(++count);
                    Thread.Sleep(1000);
                }
            });

            Console.WriteLine("Press Ctrl + C to cancel!");
            Console.CancelKeyPress += ((s, a) =>
            {
                Console.WriteLine("Bye!");
                _closingEvent.Set();
            });

            _closingEvent.WaitOne();
        }
    }
}

Adding Dependency Injection registration

Now that all the refactoring is done; therefore it is time to add .Net Core DI framework to the code. Firstly, I will install the Dependency Injection NuGet package Microsoft.Extensions.DependencyInjection.

dependency injection nuget

Secondly, once the package is added, I will create a class ContainerConfiguration to register for all the classes.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Docker.Demo
{
    internal static class ContainerConfiguration
    {
        public static ServiceProvider Configure()
        {
            return new ServiceCollection()
                .AddLogging(l => l.AddConsole())
                .Configure<LoggerFilterOptions>(c => c.MinLevel = LogLevel.Trace)
                .AddSingleton<IPrintSettingsProvider, PrintSettingsProvider>()
                .AddSingleton<IConsolePrinter, ConsolePrinter>()
                .AddSingleton<ContinuousRunningProcessor>()
                .BuildServiceProvider();
        }
    }
}

Refactoring Main method

Finally, I will refactor the main method and get the reference of the ContinuousRunningProcessor from the DI container and call the Process method.

using System;
using Microsoft.Extensions.DependencyInjection;

namespace Docker.Demo
{
    class Program
    {

        static void Main(string[] args)
        {
            Console.WriteLine("Hello Docker World!");

            var serviceProvider = ContainerConfiguration.Configure();
            serviceProvider.GetService<ContinuousRunningProcessor>().Process();
        }
    }
}

Finally, I will run the application.

Conclusion

In conclusion, this blog I walked through how to use DI container in a .Net Core console application.

Above all, the code is available in: http://github.com/choudhurynirjhar/docker-demo

In addition to the code, below is the YouTube video. https://youtu.be/2TgWRfOnOc0