Dependency Injection in a DotNetCore Console Application

In my last post I talked about creating a .NET Core console application for removing advertisements from a recorded stream.  I glossed over some of the things I did in that application such as hooking up dependency injection and configuration management.  Today I’m going to talk about implementing dependency injection in a DotNetCore console application.

Code for this post can be located on my GitHub so feel free to cross-reference it at any time.

Initial Setup

To get this party started we need to build a new .NET Core console application.  I like to work from Visual Studio (full) but let’s go ahead and use just the console today.  Type dotnet new console -n MyConsoleApplication where -n [name] is the name you want to call it.  This is going to create a project folder with that name and scaffold the basic application.

Create dotnet console application

Now that we have our application ready let’s go ahead and open your project.  I’m going to pretend you already have a favorite way of doing so… maybe VS Code or VS Community/Pro, etc.  Since I didn’t use VS Full to create the project I think I’ll use VS Code for this exercise.

Load VS Code

Getting Started

Ok, now that we have VS Code open and a templated slate to work from we need to get a couple nuget packages installed.  You can do it the cheater way or one by one.  Cheater way is open up your MyConsoleApplication.csproj file and simply paste in the following block before the closing </Project> tag.

<ItemGroup>
	<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
	<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
</ItemGroup>

If you want to do it one-by-one you can run the following command from the VS Code Terminal window: dotnet add package Microsoft.Extensions.Configuration and so on.  If you choose the cheater way you might want to run dotnet restore before we continue.

Hooking up Dependency Injection

I’m opening up Program.cs and adding the following lines of code to my Main(...) method call: 

try
{
	var services = new ServiceCollection();
	services.AddTransient<Application>();
	
	var provider = services.BuildServiceProvider();

	var application = provider.GetService<Application>();
	application.Run();
}
finally
{
	Console.WriteLine("\r\nPress any key to exit...");
	Console.ReadKey();                
}

Let’s also add the Application.cs file with the following contents:

using System;

namespace MyConsoleApplication
{
    public class Application
    {
        public Application() { }

        public bool Run()
        {
            Console.WriteLine("I'm running yo!");

            return true;
        }
    }
}

Let’s pause just a moment and go over what we’ve done here.  We’re creating the services collection using the built-in DI container support from .NET Core.  We’ve registered our Application with the container and then built the provider.  Next we ask the provider to give us an instance of Application.  Look, I get it, this is not terribly exciting and frankly a lot more work than simply doing var application = new Application() amirite?  Let’s run it anyway.  Type dotnet run into your terminal and view your application in all it’s glory!

Adding Configuration Management

If your application has some depth to it you probably want a configuration file.  This kind of stuff “just happens automatically” in a DotNetCore website using the WebHost.CreateDefaultBuilder.  If you’re curious you can look here

Anyways, it just so happens that this will make our dependency injection stuff a little more interesting.  Let’s go ahead and install the Microsoft.Extensions.Configuration.Json  and Microsoft.Extensions.Options.ConfigurationExtensions packages.  This enables us to hook up a JSON configuration file.

Now let’s add a block of code to actually register that sucker.  This is going to load the application.json file from the currently running folder and instructs that it is required and it will reload if the file has changes.

var builder = new ConfigurationBuilder()
	.SetBasePath(Directory.GetCurrentDirectory())
	.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

IConfigurationRoot configuration = builder.Build();
// ... new ServiceCollection, AddTransient ... //
services.Configure<MySettings>(configuration.GetSection("MySettings");

I’ve also added an appsettings.json 
file with { "MySettings": { "IsCheesy": false, "Name": "George" } } and a MySettings.cs file like so:

using System;

namespace MyConsoleApplication
{
    public class MySettings
    {
		public bool IsCheesy { get; set; }
		public string Name { get; set; }
    }
}

Let’s also modify our Application.cs file and make our constructor look like: 

private readonly IOptions<MySettings> _settings = null;

public Application(IOptions<MySettings> mySettings)
{
	// you can optionally do .Value but that makes it so updates in the JSON file won't reload in this instance
	_settings = mySettings;
}

Ok now let’s put it together.  We’re injecting those options that we put into our DI container during Program.cs, now lets go ahead and use them.  Make the following modifications to and around your Run() method.

protected MySettings Settings => _settings.Value;

public bool Run()
{
	if (!string.IsNullOrWhiteSpace(Settings.Name))
		Console.WriteLine($"Hello {Settings.Name}.");

	Console.WriteLine($"This application is {(Settings.IsCheesy ? "Cheesy" : "Not Cheesy")}");
	return true;
}

Extending Configuration

Any configuration is only about as good as it’s extensibility.  What do I mean by that?  What if I want some default settings for different environments?  Luckily we have plenty of ways we can go about that but let’s look at the “default” way.  

I’ve spun up a new appsettings.Development.json file and it looks like this:

{
	"MySettings": {
		"IsCheesy": true,
		"Name": "Developer Man"
	}
}

Additionally I went in and edited the MyConsoleApplication.csproj file and added the block that follows.  The purpose here is that it’ll copy these JSON files to the build directory if they are newer.

<ItemGroup>
	<Content Include="appsettings.Development.json">
		<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
	</Content>
	<Content Include="appsettings.json">
		<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
	</Content>
</ItemGroup>

In order to get this into the pipeline, however, we need to install a couple more packages and add a couple lines of code to our Program.cs.  Please install Microsoft.Extensions.Configuration.EnvironmentVariables.  Next make your Program.cs look like this:

string env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

if (string.IsNullOrWhiteSpace(env))
	env = "Development";

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

If you go run the application now you’ll see that since you’re in Development (I assume) it’ll use the settings overrides from our appsettings.Development.json file.  One thing to note is that you don’t have to include all the settings in the overrides file; it only overrides what you include.  I encourage you to learn more about these files and all the options you have with them.

Conclusion

I only very lightly touched on the subject of Dependency Injection in a DotNetCore Console Application.  Just like with any other application you have a lot of ways you can move forward here.

Let me know if this post was too code-heavy or not.  I’m still trying to get a good feel for the sweet spot here. 

Code for this article can be located at my GitHub.

Credits

Photo by Sara Bakhshi on Unsplash

2 comments on “Dependency Injection in a DotNetCore Console Application

Comments are closed.