.NET Core Console App with Dependency Injection

.Net core brought lot of great features right into the .Net framework. We get features like dependency injection, much better configuration and logging systems out of the box.

But the problem is most of the tutorials that you find online on Dependency Inject, Logging etc always assumes web application context. If someone wants to setup these things in a console application context in requires little bit more effort.

In this blog I’ll teach you how to setup Dependency Injection, Application configuration using appsettings.json and Logging using serilog in a console application.

Create console application

Visual Studio

  • Create a new project
  • Select Console App (.NET Core) C# project template
  • Type your application name
  • Select Create

Visual Studio Code

  • In a command shell, execute the following command:
    dotnet new console
  • In Visual Studio Code, open the app’s project folder.
  • When the dialog appears to add assets to build and debug the app select Yes. Visual Studio Code automatically adds the .vscode folder with generated launch.json and tasks.json files

Install extensions

Before we can add things like Dependency Injection, Settings and Logging we need to add few extensions to our project. Adding external libraries has become much simpler in .net you can install extensions via command line if you are using Visual Studio Code or If you are using Visual Studio you can install using the Nuget Package Installer UI.

My preferred way is to directly edit the csproj file esp when you know what extensions and versions you are going to use. To install the extensions simply copy the below code and paste it in your .csproj file under <Project> node and click save the file. Visual Studio will prompt a restore. Click it to confirm the restore

<ItemGroup>
	<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.7" />
	<PackageReference Include="Serilog.Extensions.Hosting" Version="3.1.0" />
	<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
	<PackageReference Include="Serilog.Sinks.Console" Version="3.1.0" />
</ItemGroup>

Configuration

Add appsettings.json file to the project and add the BuildConfig() method to your Program class

static void BuildConfig(IConfigurationBuilder builder)
{
	builder.SetBasePath(Directory.GetCurrentDirectory())
		.AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
		.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
		.AddEnvironmentVariables();
 
}

The above code adds the ability to talk to appsettings.json. Also this code expects the appsettings.json file to be on the same directory as the executable is running.

Make sure to set the appsettings.json file as Copy Always in the properties

The second json file that we are adding is mainly to support different environments. It allows us to have different configuration files for production or development. So we don’t have to store our production configuration details like connection strings, keys etc in our source code.

Setup serilog

This below code creates the serilog instance. Please check the serilog documentation on various other setup options

Log.Logger = new LoggerConfiguration()
		.ReadFrom.Configuration(builder.Build())
		.Enrich.FromLogContext()
		.WriteTo.Console()
		.CreateLogger();

Update your appsettings.json like below

{
	"LoopTimes": 5,
	"Serilog": {
		"MinimumLevel": {
			"Default": "Information",
			"Override": {
				"Microsoft": "Information",
				"System": "Warning" 
			}
		} 
	} 
}

Dependency Injection

Let’s say we have this Greeting interface and service as below

Service Interface

public interface IGreetingService
{
	void Run();
}

Service Class

public class GreetingService : IGreetingService
{
	private readonly ILogger<GreetingService> _log;
	private readonly IConfiguration _config;

	public GreetingService(ILogger<GreetingService> log, IConfiguration config)
	{
		_log = log;
		_config = config;
	}
	public void Run()
	{
		for (int i = 0; i < _config.GetValue<int>("LoopTimes"); i++)
		{
			_log.LogInformation("Run number {runNumber}", i);
		}
	}
}

In dependency engine we would be resolving the objects in the runtime. Similarly let’s say if want to resolve this GreetingService dynamically we need to inform our host builder to resolve GreetingService whenever IGreetingService is required.

var host = Host.CreateDefaultBuilder()
				.ConfigureServices((context, services) =>
				{					
					services.AddTransient<IGreetingService, GreetingService>();
				})
				.UseSerilog()
				.Build();
			
IServiceScope scope = host.Services.CreateScope();
var svc = scope.ServiceProvider.GetRequiredService<IGreetingService>();
svc.Run();

Thats it. Now if you run the application you would be seeing the Results like below

[00:50:48 INF] Application Starting
[00:50:48 INF] Run number 0
[00:50:48 INF] Run number 1
[00:50:48 INF] Run number 2
[00:50:48 INF] Run number 3
[00:50:48 INF] Run number 4

Conclusion

As you can see most of the above is a boilerplate code you could even create your own project template using the above information so that you don’t have to worry about setting this up everytime you want to create a console app

If you want a full source code please visit my Github repo

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s