Ramblings and musings of a geek

.net5 Console App

Background

Over the past 12 months I have found myself throwing together carefully crafting a .net Console App to automate a task. Creating a new Console App from within Rider (or Visual Studio) only stubs out the bare bones and is missing a few of the essentials that we take for granted when building out web apps…

  • Dependency Injection
  • Configuration
  • Logging

This article runs through the steps needed to set up these things so that I don’t have to dig through my code repos and/or hunt around the intertubes.

Dependency Injection

Dependency Injection makes for better, more maintainable code. There are many, better written pages on the web that go in to this.

So show me the code already… It doesn’t need very much code at all (always nice).

This requires just the one package…

  • Microsoft.Extensions.DependencyInjection
using Microsoft.Extensions.DependencyInjection;

namespace MyConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // Setup DependencyInjection using the basic MS ServiceCollection
            var serviceCollection = new ServiceCollection();
            
            // We'll add all of our services via this method (more later)
            ConfigureServices(serviceCollection);
            ...  

This creates a new ServiceCollection class and passes it to our ConfigureServices method which will set everything up.

Configuration

Being able to externalise configuration is a good thing! It is a shame that the console app template is anorexically lightlweight. Hooking in the Asp.net style appsettings.json configuration files is straightforward though.

Let’s start with the simple appsettings.json file that we want to load:

{
    "MyAppSettings": {
        "OutputDirectory": "/Users/marcbeavan/temp",
        "SomeImportantNumber": 42
    }
}

Don’t forget to flag your settings file as content and have it copied to your output directory. Csproj snippet is below. Or do it via your ide.

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

In addition to reading this config file at startup, we’ll also make it available via a strongly typed ÌmportantSettings.csclass that keeps all the relevant information together in one place.

    public class ImportantSettings
    {
        public string OutputDirectory { get; set; }
        public int SomeImportantNumber { get; set; }
    }

We will require some packages…

  • Microsoft.Extensions.Configuration.Json
  • Microsoft.Extensions.Configuration.FileExtensions (although this is implicitly referenced by the Json above)
  • Microsoft.Extensions.Options.ConfigurationExtensions
  • Microsoft.Extensions.Options (also implicitly referenced by the package above)

Add the magic to the ConfigureServices method…

        private static void ConfigureServices(ServiceCollection serviceCollection)
        {
            // Read the appsettings.json configuration file
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", false)
                .Build();

            serviceCollection.AddOptions();
            serviceCollection.Configure<ImportantSettings>(configuration.GetSection("MyAppSettings"));
            
            
            serviceCollection.AddScoped<MySpecialService>();
        }

This will read the configuration file and load the MyAppSettings information into ImportantSettings.cs. This is then regeistered with the service container so that it can be consumed by other services that require it (namely our MySpecialService in this example).

using Microsoft.Extensions.Options;
using MyConsoleApp.ValueObjects;

namespace MyConsoleApp.Services
{
    public class MySpecialService
    {
        private ImportantSettings AppSettings { get; set; }

        public MySpecialService(IOptions<ImportantSettings> appSettings)
        {
            AppSettings = appSettings.Value;
        }
        ...
    }
}

Logging

Logging is a powerful tool for debugging in production, and it provides vital data on the real-world use of your application. When things stop working, it’s the data in the logs that both the dev and operations teams use to troubleshoot the issue and quickly fix the problem.

You wont appreciate how useful application logging is until you come to need it!

After a decade or so of using log4net, I have more recently started using Serilog which is a little easier to set up, is more actively maintained and offers structured logging.

For this example we will use a rolling file logger which more often than not works well with a console app that wont necessarily have its own database sitting behind it.

We will require some more packages…

  • Microsoft.Extensions.Logging
  • Serilog.Extensions.Logging
  • Serilog.Sinks.RollingFile

Add the magic to the ConfigureServices method…

            // Setup Serilog logging
            var serilogLogger = new LoggerConfiguration()
                .WriteTo.RollingFile("MyConsoleApp.log")
                .CreateLogger();

            serviceCollection.AddLogging(builder =>
            {
                builder.SetMinimumLevel(LogLevel.Information);
                builder.AddSerilog(serilogLogger, true);
            });        

We can should inject this into our services…

        private readonly ILogger<MySpecialService> _logger;

        public MySpecialService(ILogger<MySpecialService> logger, IOptions<ImportantSettings> appSettings)
        {
            _logger = logger;
            AppSettings = appSettings.Value;
            
            _logger.LogInformation("Service created with out dir[{OutputDirectory}] and important number[{SomeImportantNumber}]", 
                AppSettings.OutputDirectory, 
                AppSettings.SomeImportantNumber);
        }        

and make log calls as liberally as required.

        _logger.LogInformation("Reporting something very useful: [{usful}]", variable.Info);

Wrapping up

That is pretty much it.

The sample application can be found on the CortexGG github.