Ronin Building Blocks – Configuration
Database, XML, CSV, JSON, INI and event to my horror hard coding are all techniques that I have seen for handling configuration information. With .NET, it has typically been the accepted practice of using the host mechanisms configuration management style, whether that is web.config, app.config or settings.xml and in general that has worked out fine but what about code portability and leveraging knowledge or skills across the hosts as we move portable libraries etc.
As you have probably guessed by now, there is a new framework released to help with this allowing us to use the same common techniques, not caring about the host, well for the consumers at least. This framework is the Microsoft.Extensions.Configuration framework and it leverages a provider system to abstract this information. This framework takes the traditional configuration key value pairs into an optional hierarchical key as we will see later. In short this is a framework that I will use for the IoT for it’s portability, surfacing of common paradigms and hierarchical structure. I will start with a basic sample in case you have not seen it before and build on that as I did with the logging framework.
I started by creating a console application and adding the configuration framework NUGET package (Microsoft.Extensions.Configuration).
To make use of the configuration framework we always start by creating an instance of the configuration builder. This is the heart of the configuration framework allowing me to associate configuration providers potentially pulling their information from a series of configuration stores. You will notice the use of the in-memory collection for the sample. This provider is a simple provider that takes a dictionary as its data source, but my code will not know the difference seeing the providers are abstracted. Using the configuration is as simple as providing a key.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(GetConfigurationData());
var configuration = builder.Build();
Console.WriteLine(configuration["Greeting"]);
Console.WriteLine($"Your name is { configuration["User:FirstName"] } { configuration["User:LastName"] }");
}
private static Dictionary<string, string> GetConfigurationData()
{
return new Dictionary<string, string>
{
{"Greeting", "Hello World!"},
{ "User:FirstName", "Chris"},
{ "User:LastName", "Clayton"},
{ "AzureStorage:ConnectionString", "UseDeveleopmentStorage=true;"}
};
}
}
Here is the output when I run the application
Now that I have the basic framework setup I want to do more than just access the data as a key value pair so I will use sections instead. By using the framework’s section functionality, I can get a nice user container with all the information I care about like a dictionary. I do this by replacing this line of code.
Console.WriteLine($"Your name is { configuration["User:FirstName"] } { configuration["User:LastName"] }");
The new code will retrieve the section of interest and use that like a dictionary.
var userSection = configuration.GetSection("User");
Console.WriteLine($"Your name is { userSection["FirstName"] } { userSection["LastName"] }");
As you can see with the output below it has not changed the content etc. but has made it so I can have multiple first names (e.g. user first name verses administrator first name) without collisions.
Now that I can use this in a safe collision free way it not be great to get this into a simple POCO, coincidently they have already thought of this, through the use of extensions methods found in the Microsoft.Extensions.Configuration.Binder NUGET package.
I have added a class named UserInformation that will contain the configuration data. I retrieve the section that the POCO will be created from and execute the get method on it.
var userInfo = configuration.GetSection("User").Get<UserInformation>();
Console.WriteLine($"Your name is { userInfo.FirstName } { userInfo.LastName }");
The data has not changed but the usage of the framework and contained configuration is a lot more intuitive.
I would say one of the most important things that are handled by a good configuration framework but I have not looked at yet encrypted values for things like my AzureStorage:ConnectionString setting. Although in this case I am just using the emulator the reality is that if I was to use this in production a plain text connection string is bound to trigger a ton of security questions. At first glance the framework provides this mechanism out of the box for us, but that is only for production.
The final thing that I will do is look over a couple of the providers that exist out of the box for the framework that may be of interest in future development.
- Microsoft.Extensions.Configuration.Json uses a JSON files as a source
- Microsoft.Extensions.Configuration.CommandLine uses command line arguments as a source
- Microsoft.Extensions.Configuration.EnvironmentVariables uses environment variables as a source
- Microsoft.Extensions.Configuration.KeyVault uses Azure Key Vault as a source
There are many available configuration providers and if required I can customize it by creating my own. The framework is powerful and a welcome abstraction.