How to use dependency injection in a C# .NET console application

How to use dependency injection in a csharp dot NET console application
Home » Technology » .Net » How to use dependency injection in a C# .NET console application

Table of contents

My favorite type of project in .NET is console applications as they allow me to develop quickly and without complications focusing on what’s most important to me: automating work to save time on repetitive tasks. As a good developer any repetitive process that takes more than five minutes is a candidate for automation and, for that, the simplest and most effective option is a C# console application.

Another reason I choose to use a C# console application is because it doesn’t require a web server or Azure infrastructure. I simply publish it as a portable application and then use it on my computer with a double-click.

One of the steps I usually take in this type of express development is to add dependency injection to the C# console application for the same reason as before: to make development faster, more convenient and cleaner.

This article is nothing more than a personal guide with all the steps necessary to use dependency injection in a console application, so you can refer to it whenever you need.

Full example code with a console project

Let’s start with a example project in .NET 8, C# and a “Console” project type created using Visual Studio or VSCode.

First, we have the Program class, which contains two calls to two methods of the Core class:

using ConsoleApp1;

internal class Program
{
    private static void Main(string[] args)
    {        
        Core core = new Core();
        core.Method1();
        core.Method2();
    }
}

Secondly, we have the Core class or core of our C# console application with two different methods that call the database service to obtain information and display it on the screen:

using ConsoleApp1.Services;

namespace ConsoleApp1
{
    public class Core
    {
        public void Method1()
        {
            IServiceDatabase service = new ServiceDatabase1();
            Console.WriteLine("Core.Method1: " + service.GetData());
        }
        public void Method2()
        {
            IServiceDatabase service = new ServiceDatabase1();
            Console.WriteLine("Core.Method2: " + service.GetData());
        }
    }
}

Next we have an IServiceDatabase interface to define the methods that will access the database:

namespace ConsoleApp1.Services
{
    public interface IServiceDatabase
    {
        public string GetData();
    }
}

And finally we have two identical implementations of the interface, ServiceDatabase1 and ServiceDatabase2, as you can see the only thing that changes is the way of implementing the GetData method, in each case a different information is returned:

namespace ConsoleApp1.Services
{
    public class ServiceDatabase1 : IServiceDatabase
    {
        public string GetData()
        {
            return "ServiceDatabase1.GetData";
        }
    }
}

namespace ConsoleApp1.Services
{
    public class ServiceDatabase2 : IServiceDatabase
    {
        public string GetData()
        {
            return "ServiceDatabase2.GetData";
        }
    }
}

If you’re already familiar with dependency injection you’ll have realized that this is the typical approach we can use to achieve cleaner, more maintainable code and improve development speed if we need to change the implementation of a service.

Let’s think for a moment: what if I want to use the ServiceDatabase2 implementation throughout my application instead of ServiceDatabase1? Well, I have to change all the initialization locations and, instead of calling IServiceDatabase service = new ServiceDatabase1();, I have to call IServiceDatabase service = new ServiceDatabase2();. In this example these are only two lines but, in a real project, these can be hundreds of lines and this is not practical at all, it penalizes development time.

Why use dependency injection?

Given the previous reflection, let’s list some of the advantages of using dependency injection in any project, regardless of its type, language, and framework:

  • Faster code modifications, following the previous code example, we have seen that if we don’t have dependency injection and we want to modify the implementation of a service we will have to edit hundreds of lines of code.
    However, using this method, and if done correctly, making a modification consists of only modifying a single line, as we will see below.
  • Greater development speed, if we want to use the implementation of a specific service we will simply have to initialize it once and then use it in the classes we need by adding it to its constructor. We will avoid a lot of redundant or repetitive code since we won’t have as many new() statements as before.
  • Cleaner code, by saving lines of code, it will be cleaner and, consequently, more understandable.
  • More comfortable development, this point derives from all the previous ones, if the code is cleaner and modifications or insertions of new services are faster, development becomes much lighter.

How to use dependency injection in a C# console application?

Now comes the fun part as we’ll put everything we’ve described above into practice. We’ll start with the full example code where we have nothing and we’ll follow the steps to use dependency injection in a C# console application:

  • Make sure the Program class has the correct structure. Generally, when we create a console application, the Program class comes without the Main method. Make sure it looks similar to the following code:
    internal class Program
    {
        private static void Main(string[] args)
        {        
            Core core = new Core();
            core.Method1();
            core.Method2();
        }
    }
  • Install the Microsoft.Extensions.DependencyInjection package via the NuGet package manager.
  • Change the Program class to register the services you need, including the Core class. Don’t forget to add the using Microsoft.Extensions.DependencyInjection; line:
    using ConsoleApp1;
    using ConsoleApp1.Services;
    using Microsoft.Extensions.DependencyInjection;
    
    internal class Program
    {
        private static void Main(string[] args)
        {
            // Register services
            var services = new ServiceCollection();
            services.AddSingleton<IServiceDatabase, ServiceDatabase1>();
            services.AddSingleton<Core>();
            var serviceProvider = services.BuildServiceProvider();
    
            // Call core methods
            Core core = serviceProvider.GetService<Core>();
            core.Method1();
            core.Method2();
        }
    }
  • Update the Core or main class so that it receives, by dependency injection, the database service or the corresponding one:
    using ConsoleApp1.Services;
    namespace ConsoleApp1
    {
        public class Core
        {
            // Get the service database instance through dependency injection
            private IServiceDatabase _serviceDatabase;
            public Core(IServiceDatabase serviceDatabase)
            {
                _serviceDatabase = serviceDatabase;
            }
    
            // Methods from core
            public void Method1()
            {
                Console.WriteLine("Core.Method1: " + _serviceDatabase.GetData());
            }
            public void Method2()
            {
                Console.WriteLine("Core.Method2: " + _serviceDatabase.GetData());
            }
        }
    }
  • Check that the dependency injection is correct, to do this run your program and verify that the output is from service 1 and not service 2:
    Core.Method1: ServiceDatabase1.GetData
    Core.Method2: ServiceDatabase1.GetData
  • Change the dependency injection so that service 2 is injected instead of service 1. To do this, within the Program class, change the line services.AddSingleton<IServiceDatabase, ServiceDatabase1>(); to services.AddSingleton<IServiceDatabase, ServiceDatabase2>();.
  • Check that the change is correct, the output should now be as follows:
    Core.Method1: ServiceDatabase2.GetData
    Core.Method2: ServiceDatabase2.GetData

Note carefully that the advantages of dependency injection we discussed above are met, since changing the implementation of a service across the entire application only required one line.

On some websites you’ll find that the Microsoft.Extensions.Hosting package and code similar to var host = Host.CreateDefaultBuilder() are required; this isn’t necessary at all, at least not to easily and effectively add dependency injection to your C# console application. It will only complicate your code unnecessarily.

Finally, here are the links to the full code examples used in this step-by-step guide: