.NET — Creating advanced console applications

Adding support for Dependency Injection, Logging and Configurations using the NuGet SimpleSoft.Hosting

Life as a .NET developer never looked brighter and more exciting. This last few years have been amazing: .NET Standard, ASP.NET Core, oficial Linux and Mac support, open source everywhere, what more can we ask? Well, one thing that always seems to be forgotten are console application, despite their important role.

In this article I’ll explain how can someone create more complex or fasten the development of console applications by using a small library named SimpleSoft.Hosting.

Why complex console applications?

Ever since I entered the .NET world, my main focus was on all sorts of web applications, but over time I also found console applications to be an important part of my job, specially in automating stuff. From basic installers (copy some files, change some registries, stop some services), cleanup jobs (compress or delete log files), to Windows Services using wrappers like NSSM that can be useful for self-hosting Web APIs or update managers, console applications are an important part of my developer life.

When creating a console application I typically need the following:

  • Logging: this is a requirement for me. Not having feedback in what’s happening and only relying in Console.WriteLine is not enough. I always use a library like NLog or similar to, at the bear minimum, write everything into a file;
  • Settings: most of the times I need application settings that can be easily configured (sometimes even per environment) or consumed from the command line arguments;
  • Dependency Injection: for simple applications this may not be a requirement, but when self-hosting an API or running as a Windows Service this becomes very important. Besides, I’m so used to it that my mind makes it a requirement;

My console applications setup were usually like this (assuming I was using the new Microsoft.Extensions.* packages):

Example of a console application with logging, settings and dependency injection

As you can see, this is a lot of boilerplate code that, even when using Visual Studio templates to make it fast to setup, most of it is completely unnecessary and could be easily reused.

What are the SimpleSoft.Hosting packages?

When I started some months ago the library SimpleSoft.Database.Migrator, due to a project requirement, I immediately realized that the hosting package, with some cleanups, could be a library by itself that could help with the hosting and setup of console applications. So I decided in this last few weeks to extract everything into NuGet packages with the SimpleSoft.Hosting namespace and implement something that should fulfill the following goals:

  • Familiar API: any developer that have worked with ASP.NET Core Hosting will immediately know how to configure this library. Others should find it easy to understand. Wiki documentation is also on its way;
  • Logging facade: because I wanted to support any log framework, I decided to use the recent Microsoft.Extensions.Logging packages. With this choice, the library immediately supports console, debug or event viewer logs out of the box, and because it is also being used in ASP.NET Core applications, there are adapters from all major logging libraries out there;
  • Application settings: for this one, Microsoft.Extensions.Configurations is a natural fit. It has support for JSON, XML, even INI files, environment variables and command line arguments and has replaced the old ConfigurationManager;
  • Dependency Injection facade: using Microsoft most recent dependency injection facade packages (Microsoft.Extensions.DependencyInjection), the library is able to support the common use cases, like singletons, transients and scoped life cycles, while also allowing for other containers to be used, like AutoFac of SimpleInjector;
  • Support .NET Standard: it should be able to run in as many machines as possible, so support for .NET Standard was a requirement;

Using the SimpleSoft.Hosting packages

So, how can you use this library? Here is the same code I presented earlier, but using the HostBuilder API:

Example of a console application with logging, settings and dependency injection using SimpleSoft.Hosting packages

As you can see, almost all boilerplate code has vanished (it could be even more simplified if I didn’t want to log and differentiate cancellations from other exceptions). How does it work?

  1. When the builder is created, it will search the environment variables for a key named ENVIRONMENT to know the current environment name. If not found, the Production value will be used. A custom key name may be passed or even an IHostingEnvironment instance, with a custom name andIFileProvider;
  2. Setting up an already configured ILoggerFactory ensures that you can log every debug message from the builder (in this case to the console) as soon as you start building a host. Typically this isn’t required but it is usually my approach;
  3. When building a host, the IConfigurationBuilder handlers are the first thing to be run. This enables to load settings from files, environment variables, command line arguments or anything you may need;
  4. After building the IConfigurationRoot, it will run all configuration handlers. This allows you to manually add settings, like decreasing some cache settings if in development or decipher some entry;
  5. Then the handlers for configuring the ILoggerFactory are run. This makes it easier to add third-party libraries and reading settings from the IConfiguration instance;
  6. Then, after having all the application settings available and support for logging, you can register your own services into the dependency container. The host builder automatically adds the ILoggerFactory, IConfigurationRoot and IHostingEnvironment instances as singletons;
  7. Then you can use your own IServiceProvider implementation that may wrap your favorite container, like AutoFac or SimpleInjector;
  8. Finally there is a collection of handlers for configuring registered services;
  9. When building a host type, it will be registered as a scoped service and an IHostRunContext<THost> will be returned, making sure that at least a global scope exists for the application;

Can I reduce even more the main method?

So, you want to reduce even more the code in the Main method? Just like ASP.NET Core Hosting supports a startup class, here I also provide a similar solution:

Example of a host builder that uses a startup class

Please note that I’m not the biggest fan of duck typing, specially when it doesn’t bring much to the table, so right now I prefer to force the implementation of an IHostStartup interface (the abstract class HostStartup makes it possible to only override the needed methods). If enough requests are made, I may support a more dynamic approach like the one from ASP.NET Core.

So, the next time you are creating a console application, feel free to give this library a good try if, like me, you need some advanced features and don’t want to repeat yourself.

Have a question or a suggestion? Just send me a direct message, email, tweet or open an issue in the GitHub repository page. Happy coding!