27.04.2022

How we write console applications

Last month, we open-sourced our base library for creating console applications and published it as a NuGet package. At the same time, we overhauled the library for the first time since we internally introduced it two years ago at Neolution. Since then, it has been used as the base library for command line tools in various projects for many of our customers. Typically, these types of applications run automated tasks and are either scheduled or executed on demand.

 

The objective for creating this library was to improve developer experience by providing a common structure and reducing the amount of boilerplate code. To achieve this, we took inspiration from the basic project structure and the bootstrapping process Microsoft uses for its ASP.NET Core application template, and then built our own (web) host builder equivalent. We also extended it to let developers pass a composition root (such as Startup.cs) to it, so with this library, every console application has dependency injection enabled by default and there is a central and predictable class that holds the configurations.

 

For this next iteration, we wanted to further reduce boilerplate code, but also to nudge the developers into writing more modular code in console applications. One common headache for developers was writing parsing logic for command line arguments. Luckily, there are already solutions out there, such as the excellent CommandLineParser library, that provide a way to solve that issue. So, we added it to our library and leveraged their verb commands concept into what we call console app commands. With console app commands, developers can now isolate code by tying it to a specific verb and its options.

 

To use it, just create two classes. One for the console app command, which will contain your code, and one class that contains the definition of the command line verb and its options.

 

For the options class, you can use all the features that CommandLineParser provides. The only requirement when using it with our library is that you must define a verb for it.

 

For the console app command, just implement from the IConsoleAppCommand<TOptions> interface and place your logic in the Run(TOptions) command that is required by that interface. If your logic depends on services that are available through dependency injection, they can be retrieved via constructor injection because console app commands are automatically picked up by the built-in dependency injection container during the start-up of the console application.

 

If you’d like to try it yourself, you can fork our repository on GitHub and run the sample application.