If you search the official .NET documentation, you will probably not find much information on how to add config files to your .NET Core console applications. Let's learn how.
Photo by Christopher Gower on Unsplash |
Why replicate ASP.NET Configuration
The maturity that the .NET Core framework achieved includes the configuration framework. And all of that, despite the lack of documentation, can be shared between web and console apps. That said, here are some reasons why you should be using some of the ASP.NET tolling on your console projects:- the configuration providers read configuration data from key-value pairs using a variety of configuration sources including appsettings.json, environment variables, and command-line arguments
- it can be used with custom providers
- it can be used with in-memory .NET objects
- if you're developing with Azure, integrates with Azure Key Vault, Azure App Configuration
- if you're running Docker, you can override your settings via the command line or environment variables
- you will find parsers for most formats (we'll see an example here)
The Solution
So let's take a quick look at how to integrate some of these tools in our console apps.Adding NuGet packages
Once you create your .NET Core app, the first thing to do is to add the following packages:- Microsoft.Extensions.Configuration
- Microsoft.Extensions.Configuration.Binder
- Microsoft.Extensions.Configuration.EnvironmentVariables
- Microsoft.Extensions.Configuration.FileExtensions
- Microsoft.Extensions.Configuration.Json
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true)
.AddJsonFile($"appsettings.{env}.json", true, true)
.AddEnvironmentVariables();
var config = builder.Build();
var builder = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true)
.AddJsonFile($"appsettings.{env}.json", true, true)
.AddEnvironmentVariables();
var config = builder.Build();
If set, the env var above will auto-load the configuration as per the environment variable ASPNETCORE_ENVIRONMENT that comes preset on a new ASP.NET Core project. So for dev, it will try to use appSettings.Development.json sticking with appSettings.Development.json if the former doesn't exist.
Creating a configuration file
Now add an empty appSettings.json file in the root of your project and add your configuration. Remember that this is a json file so your config should be a valid json document. For example, to config file for one of my microservices is:
{
"MassTransit": {
"Host": "rabbitmq://localhost",
"Queue": "hildenco"
},
"ConnectionString": "Server=localhost;Database=hildenco;Uid=<username>;Pwd=<pwd>",
"Smtp": {
"Host": "<smtp-server>",
"Port": "<smtp-port>",
"Username": "<username>",
"Password": "<password>",
"From": "HildenCo Notification Service"
}
}
"MassTransit": {
"Host": "rabbitmq://localhost",
"Queue": "hildenco"
},
"ConnectionString": "Server=localhost;Database=hildenco;Uid=<username>;Pwd=<pwd>",
"Smtp": {
"Host": "<smtp-server>",
"Port": "<smtp-port>",
"Username": "<username>",
"Password": "<password>",
"From": "HildenCo Notification Service"
}
}
Parsing the configuration
There are two ways to access the configuration: by accessing each entry individually or by mapping the whole config file (or specific sections) to a class of our own. Let's see both.Accessing config entries
With the config instance above, accessing our configurations is now simple. For example, accessing a root property is:
var appName = config["ConnectionString"];
While accessing a sub-property is:
var rmqHost = config["RabbitMQ:Host"];
Mapping the configuration
Despite working well, the previous example is verbose and error prone. So let's see a better alternative: mapping the configuration to a POCO class that Microsoft calls the options pattern. Despite its fancy name, it's probably something that you'll recognize.We'll also see two examples: mapping the whole configuration and mapping one specific section. For both, the procedure will require these steps:
- creating an options file
- mapping to/from the settings
- binding the configuration.
Mapping the whole config
Because our configuration contains 3 main sections - MassTransit, a MySQL ConnectionString and a SMTP config -, we'll model our AppConfig file the same way:
public class AppConfig
{
public SmtpOptions Smtp { get; set; }
public MassTransitOptions MassTransit { get; set; }
public string ConnectionString { get; set; }
}
SmtpOptions should also be straight-forward:{
public SmtpOptions Smtp { get; set; }
public MassTransitOptions MassTransit { get; set; }
public string ConnectionString { get; set; }
}
public class SmtpOptions
{
public string Host { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
As MassTransitOptions:{
public string Host { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
public class MassTransitOptions
{
public string Host { get; set; }
public string Queue { get; set; }
}
The last step is binding the whole configuration with our config:{
public string Host { get; set; }
public string Queue { get; set; }
}
var cfg = config.Get<AppConfig>();
Accessing Configuration Properties
With the config loaded, accessing our configs becomes trivial:
var cs = cfg.ConnectionString;
var smtpFrom = cfg.Smtp.From;
var smtpFrom = cfg.Smtp.From;
Mapping a Section
To map a section we use the method .GetSetcion("<section-name>").Bind() present on the Microsoft.Extensions.Configuration.Binder NuGet package that we added earlier. For example, to map just SmtpOptions we'd do:
var mailOptions = new SmtpOptions();
config.GetSection("Mail").Bind(mailOptions);
config.GetSection("Mail").Bind(mailOptions);
Making it Generic
Turns out that quickly the previous procedure also gets verbose. So let's shortcut it all with the following generic method (static if ran from Program.cs):
private static T InitOptions<T>(string section)
where T : new()
{
var config = InitConfig();
return config.GetSection<T>(section);
}
And using it with:where T : new()
{
var config = InitConfig();
return config.GetSection<T>(section);
}
var smtpCfg = InitOptions<SmtpConfig>("Smtp");
Reviewing the solution
Everything should be good at this point. Remember to leverage your options classes along with your Dependency Injection framework instead of accessing the IConfiguration for performance reasons. To conclude, here's our final program.cs file:
static async Task Main(string[] args)
{
var cfg = InitOptions<AppConfig>();
// ...
}
private static T InitOptions<T>()
where T : new()
{
var config = InitConfig();
return config.Get<T>();
}
private static IConfigurationRoot InitConfig()
{
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true)
.AddJsonFile($"appsettings.{env}.json", true, true)
.AddEnvironmentVariables();
return builder.Build();
}
{
var cfg = InitOptions<AppConfig>();
// ...
}
private static T InitOptions<T>()
where T : new()
{
var config = InitConfig();
return config.Get<T>();
}
private static IConfigurationRoot InitConfig()
{
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true)
.AddJsonFile($"appsettings.{env}.json", true, true)
.AddEnvironmentVariables();
return builder.Build();
}
Conclusion
On this post we reviewed how to use the ASP.NET tooling to bind and access configuration from our console applications. While .NET Core matured a lot, the documentation for console applications is not that great. For more information on the topic I suggest reading about Configuration in ASP.NET Core and understanding .NET Generic Host.References
- Configuration in ASP.NET Core
- The Options Patterns in ASP.NET Core
- Dependency injection in ASP.NET Core
- IConfiguration
- Use multiple environments in ASP.NET Core
See Also
- Microservices in ASP.NET
- My journey to 1 million articles read
- Adding Application Insights telemetry to your ASP.NET Core website
- Creating ASP.NET Core websites with Docker
- Deploying Docker images to Azure App Services
- Send emails from ASP.NET Core websites using SendGrid and Azure
- Distributed caching in ASP.NET Core using Redis, MongoDB and Docker
- Build Docker images on GitHub using GitHub Actions
- Async Request/Response with MassTransit, RabbitMQ, Docker and .NET core
- Hosting NuGet packages on GitHub
- How to build and run ASP.NET Core apps on Linux
- How to create a Ubuntu Desktop on Azure
- How I fell in love with i3