Build a Console App in .NET Core Like a Pro!

If you have worked with .NET before chances are you’ve interacted with a console application at some point. Console applications can range from small helper utilities to full on command-line tooling.  In .NET Core you can create a new console application with the .NET Core CLI or with the Visual Studio “New Project” menu item. With either approach, you get a basic “Hello World” application.  In some cases, this might be a perfectly acceptable starting point.  Other times, you may want a few more features before you get started. 

In most console applications I create, there are a couple additional features I like to add outside what is provided in the default template. With previous versions of the .NET Framework, there is a bit of plumbing that was needed to include these features.  In .NET Core, many of these common components can easily be included as extension packages.  In this article, we will discuss how you can get maximum gains by adding the following items to your .NET Core Console Applications.

  • Configuration Settings
  • Dependency Injection
  • Async Main

Configuration Settings

Adding an appsettings configuration file (appsettings.json) is a great way to allow behavior to be altered without changing code or recompiling the application. Since the beginning of the .NET Framework, this could be done with an application configuration (app.config) file.  That said, there are a lot of drawbacks using a traditional app.config file.

  • Loosely typed – Each setting value needs to be parsed in the application. This is typically done with a series of TryParse functions.
  • Configuration Galore – All application configuration is stored in a single file. This includes settings as well as connection strings, binding redirects, security settings, etc.
  • No Overrides – There is no configuration hierarchy. This means settings cannot be overridden with things like environment variables.

In ASP.NET Core, a new configuration mechanism was introduced with the appsettings.json file. This new approach to application configuration solved many of the drawbacks of that were previously encountered with the app.config file. The most obvious is the new file stored in JSON!  In a .NET Core application an appsettings.json file can be adopted easily with a couple extension packages.

  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration.FileExtensions
  • Microsoft.Extensions.Configuration.Json
  • Microsoft.Extensions.Configuration.Binder

With .NET Core, there are three different ways to install a nuget package.

Now with the packages added to your project you can include an appsettings.json file.  For the sake of an example, we will add the following contents.

{
    "author": {
        "firstName": "Jason",
        "lastName": "Robert"
    }
}

Now the nice thing about using the .NET Core Configuration API is we can take this JSON and easily parse it into a configuration object.  Continuing with our example, we will create a “Person” object to store the configuration in.

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

With our appsettings.json file and new Person class, we just need to do a little bit of configuration at startup to tie everything together.  The following snippet shows how this can be achieved with only a few lines of code!

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;

namespace JrTech.Core.ConsoleApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder()
             .SetBasePath(Directory.GetCurrentDirectory())
             .AddJsonFile("appsettings.json");

            var config = builder.Build();
            var author = config.GetSection("author").Get<Person>();

            // Removed for brevity
        }
    }
}

In addition to the Json Configuration Provider, which is utilized in the previous example, the .NET Core Configuration API also offers several other configuration providers. 

When multiple configuration providers are being used, you can take advantage of the ConfigurationBuilder’s fluent interface and chain together multiple configuration sources.  For example, if we add the Microsoft.Extensions.Configuration.EnvironmentVariables package, we can include it as a configuration source.  In the following example, any environment variables that are found with the JRTech_ prefix will override values that are found in the appsettings.json file.

var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .AddEnvironmentVariables("JRTech_");

As you can see, the .NET Core Configuration API is extremely powerful and provides applications with configuration rich functionality out of the box. This is leaps and bounds better than the configuration mechanisms that were available in previous versions of the .NET Framework.

Dependency Injection

Dependency injection is one of the most prevalent design patterns in modern software development. ASP.NET has long supported dependency injection by allowing an IOC container to be wired up as a “DependencyResolver”. ASP.NET Core comes with its own IOC container and a simple means for configuring it. Like the Configuration API, the dependency injection framework is something that can also be utilized in a .NET Core Console Application as well.

The first step is including in the appropriate extension packages.  As mentioned above, nuget packages can be added with the .NET Core CLI OR the Nuget Package Manager. For dependency injection, there is only a single package that is needed in order to get started.  

  • Microsoft.Extensions.DependencyInjection

Once our package is added, we just need to wire up our dependencies.  We can do this by adding the following code to our Program.Main function. If we have a lot of dependencies that we don’t want to manually configure, we can use a library like Scrutor.  I recently wrote a post on using Dependency Injection in ASP.NET Core with Scrutor.  Feel free to use it as a reference point but, for simplicity, I will not be using Scrutor in the following example.

var serviceProvider = new ServiceCollection()
    .AddSingleton<IAppHost, AppHost>()
    .AddSingleton<IMailService, SmtpService>()
    // Add other dependencies here ...
    .BuildServiceProvider();

For those who are not familiar with using an IOC Container, we telling the service provider to map each of the given “services” to an instantiable implementation class.  This allows us to request a service (typically an interface) and retrieve an implementation of that interface.  This is performed by dependency injection, typically via constructor parameters. In doing so, each class does not need to rely directly on class implementations.  We can instead only reference the interfaces of those classes.  This allows us to produce, clean and testable code. 

For further reading, look up the Dependency Inversion Principle which is the ‘D’ SOLID Principles.  Now, back to our example.  With all the mappings added and our service provider created, we can use it to request service instance(s).  We can do this by using the serviceProvider.GetService<T>() function as shown below.

var appHost = serviceProvider.GetService<IAppHost>();

Now the beauty of using an IOC Container is not only does this create an instance of the requested service, it creates an instance of each of its dependencies, its dependencies dependencies, etc.  For instance, if AppHost had the following implementation, it would also create an instance of the IMailService implementation (SmtpService) and provide it via constructor injection.

public class AppHost
{
    private readonly IMailService _mailService;

    public AppHost(IMailService mailService)
    {
        _mailService = mailService;
    }

    public void Run()
    {
        // Removed for brevity
    }
}

“So what’s up with this AppHost class?” you might be thinking.  Well, console applications are a little different than a web application in terms of how they interact with the IOC Container.  In ASP.NET Core for instance, with each request, a controller (and its dependencies) are instantiated by the service provider.  This is all handled by ASP.NET Core at the framework level.  Since vanilla console applications do not have the concept of ASP.NET Core request life cycles, I like to create my own “entrypoint”.  This is where the AppHost class comes into play.  Typically, I will include all of my “application logic” in the AppHost and leave all of the service provider configuration in my Main method.  This leaves me with a Program.Main method that looks something like this.

    public class Program
    {
        public static void Main(string[] args)
        {
            // Setup the container
            var serviceProvider = new ServiceCollection()
                .AddSingleton<AppHost, AppHost>()
                .AddSingleton<IRandomNumberGenerator, RandomNumberGenerator>()
                // Add other dependencies here ...
                .BuildServiceProvider();

            // Run our host
            serviceProvider.GetService<AppHost>().Run(args);
        }
    }

Async Main

The last feature is less of a framework feature and more of a language feature.  With version 4.0 of the .NET Framework, Microsoft introduced the Task Parallel Library (TPL) as the preferred means of writing mutlithreaded asynchronous code.  Shortly thereafter, C# 5 was introduced and provided two new keywords which made programming with TPL vastly easier.  Those two keywords are async and await.  These new features made it simple for developers to execute previously “blocking” activities while keeping the application responsive.

Working with the Task Parallel Library and using the async/await keywords is a topic in itself.  Fortunately, there is a breadth of information on the internet so, we won’t go into details here.  What I will mention is how popular this programming paradigm has become.  Over the last couple of years many frameworks and libraries (including those provided by the .NET Framework) offered both sync and async versions.  With the popularity of TPL, many libraries are now only offering async versions of their key operations.  The 
System.Net.HttpClient is a prime example.

It can be inconvenient at times switching between async and non-async functions.  In the past, if you wanted to call an async method from your console’s Main method, you needed to add some boilerplate code as shown below.

public static void Main(string[] args)
{
    FooBarAsync().GetAwaiter().GetResult()
}

C# 7.1 allows async Main functions.  In this case, the compiler takes care of boilerplate code allowing you to write the previous example like the following example.

public static async Task Main()
{
    await FooBarAsync();
}

If you get a compiler warning, make sure you are using C# 7.1.  You can check the language version a couple different ways.

References