.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):
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 oldConfigurationManager
; - 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:
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?
- 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, theProduction
value will be used. A custom key name may be passed or even anIHostingEnvironment
instance, with a custom name andIFileProvider
; - 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; - 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; - 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; - Then the handlers for configuring the
ILoggerFactory
are run. This makes it easier to add third-party libraries and reading settings from theIConfiguration
instance; - 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
andIHostingEnvironment
instances as singletons; - Then you can use your own
IServiceProvider
implementation that may wrap your favorite container, like AutoFac or SimpleInjector; - Finally there is a collection of handlers for configuring registered services;
- 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:
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!