We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept
Card image cap

Dependency Injection in a .NET Core Console Application

ASP.NET Core  • Posted 23 days ago

Off late, we've been looking at making full use of the dependency injection container to inject service instances into requesting components in an aspnetcore web application. During the webapplication bootstrap, we provide all the necessary configurations in the Startup class and use it in building a IWebHost on which the web application runs in a self-hosted manner. But things turn out a lot different when working on a console application in dotnetcore, where there's no WebHost for that manner.

But there can be scenarios where we would want to have the same efficiency and simplicity of object management offered by a DI container for using object instances in a console application without having to worry about the lifetimes and memory management for these created instances, which would generally be taken care by the DI. Let's look at how we can introduce dependency injection in a dotnetcore console application using the same DI container provided with an aspnetcore web application.

Setting up the Context:

For our example, let's assume we have a Service called SomeService which has a single method DoProcess(). This DoProcess() method simply reads a value from the appsettings.json and puts it on the console output. Things turn interesting, when we would want to have the Configuration instance which keeps hold of the values from the appsettings.json file be "injected" into the SomeService class, and this SomeService class shall not be instantiated directly for invocation. Instead, we would have an abstraction ISomeService which the SomeService class implements and we would call DoProcess() method on a variable of type ISomeService. Let's look at how we do it on a dotnetcore console application.

To get started, let's create a new console application. Note that we're using dotnetcore 3.1 sdk for our project and so all the dependencies required shall be installed in their respective versions.


> dotnet new console --name DiConsoleApp

Once the project is created, open the project in a Visual Studio Code window and start by adding an appsettings.json file. Note that since a console application doesn't come with any such file by default template, we would want to add one (since we're so accustomed with that right?).

Next, we would want to install a few dependencies, which would make our DI goal possible. You can just add these lines to the csproj file or manually add them via CLI -


dotnet add package <package_name>

-or-


<ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.3" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.3" />
</ItemGroup>

What we're adding here are basically extensions for Configuration and Hosting. How they're helpful? we'd find out as we move forward.

Next, in addition to the already existing Program.cs where the main() method exists, we'll add two more class files ISomeService and SomeService which are defined as we described before as below:


namespace DiConsoleApp
{
    public interface ISomeService
    {
        void DoProcess();
    }
}


using System;
using Microsoft.Extensions.Configuration;

namespace DiConsoleApp
{
    public class SomeService : ISomeService
    {
        IConfiguration configuration;

        public SomeService(IConfiguration configuration)
        {
            this.configuration = configuration;    
        }

        public void DoProcess()
        {
            var value = configuration["SomeKey"];
            Console.WriteLine("Value from the Config is: " + value);
        }
    }
}

Observe that we'd injected the IConfiguration via constructor similar to a webapplication scenario and we'd added a namespace reference for Microsoft.Extensions.Configuration.

Also, we're trying to access a key "SomeKey" from the appsettings.json file, which is something like below:


{
    "SomeKey": "This is from Config!"
}

Now that we're all set with the logic part for expected output, let's wire up the DI container. To do this, let's add another class Startup.

In the Startup class, add a constructor and first build the Configuration as below.


public Startup()
{
    var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

        configuration = new ConfigurationBuilder()
                        .SetBasePath(Directory.GetCurrentDirectory())
                        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                        .AddJsonFile($"appsettings.{environment}.json", optional: true)
                        .AddEnvironmentVariables()
                        .Build();
}

Observe the various method chains we've added on top of the ConfigurationBuilder() instance. We'd added a SetBasePath() which defines where to look for the files to be specified next. We've add the AddJsonFile() which specify the json files which are to be taken to build the IConfiguration dictionary. We've added the AddEnvironmentVariables() to take into consideration the environmental variables if any. Finally we're invoking the Build() on this whole recipe. Once done, we've got our IConfiguration with all our key-values from appsettings.json and other sources ready to be accessed.

We'd next build a ServicePipeline for the DI container to digest. For this, we would need the IServiceCollection we generally use in ConfigureServices() method in a web application Startup class. This is straight forward, as:


/* Startup constructor */

// instantiate
var services = new ServiceCollection();

// add necessary services
services.AddSingleton<IConfiguration>(configuration);
services.AddSingleton<ISomeService, SomeService>();

// build the pipeline
provider = services.BuildServiceProvider();

Observe that we've pushed the configuration object we've just built into a Singleton service for IConfiguration. Now wherever the IConfiguration is injected, this configuration object will be substituted. Simple! We've also added the SomeService as a Singleton to be substituted in place of ISomeService wherever requested.

Finally, we'd just build the service pipeline and assign it to a provider variable. This we would maintain as class variables as below:


using System;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace DiConsoleApp
{
    public class Startup
    {
        private readonly IConfiguration configuration;
        private readonly IServiceProvider provider;

        /* constructor comes here */
    }
}

Observe that we're using the extension Microsoft.Extensions.DependencyInjection where the type IServiceCollection and others reside. To finish things off, we'd add two public properties for these private variables configuration and provider as below:


/* Startup class */

// access the built service pipeline
public IServiceProvider Provider => provider;

// access the built configuration
public IConfiguration Configuration => configuration;

How do we test this setup? We'd need to instantiate the SomeService() class and call DoProcess() method. For this within the Program.Main() method, we'd start like this.


using System;
using Microsoft.Extensions.DependencyInjection;

namespace DiConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // instantiate startup
            // all the constructor logic would happen
            var startup = new Startup();

            // request an instance of type ISomeService
            // from the ServicePipeline built
            // returns an object of type SomeService
            var service = startup.Provider.GetRequiredService<ISomeService>();

            // call DoProcess on the ISomeService type
            // should print value for SomeKey on console
            // fetched from IConfiguration
            // injected into the class via DI
            service.DoProcess();
        }
    }
}

Pretty much self-explanatory i believe. Observe that we'd added the using statement of DependencyInjection again here because, the GetRequiredService() method on the ServiceProvider is an extension from the Microsoft.Extensions.DependencyInjection namespace, so we'd definitely need that using statement to access this method.

When we run this whole setup, it gives us the below output.


:\DiConsoleApp>dotnet run
Value from the Config is: This is from Config!

This way we can introduce and leverage the capabilities of DI into a console application using the same way as we'd do in a web application, with a little more effort than the latter. We can extend to almost all other services such as a DatabaseContext, a Logger and all.

Crazy isn't it? How'd you plan to implement this?

.net core console app dependency injection dotnet core console app dependency injection .net core console dependency injection .net core console application dependency injection .net core 3.1 console app dependency injection .net core console app dependency injection configuration dependency injection .net core console using dependency injection in a .net core console application net core console dependency injection .net core 3 console app dependency injection net core console app console application dependency injection net core dependency injection console app dotnet core 3.1 console app dependency injection dotnet core 3.1 console app appsettings.json