As the .NET Core community matures, we continue to get more options for the applications we build. This post describes how configuration works in a .NET Core application, some of the best options I’ve found, and how to configure each provider.

When setting up a new .NET Core application, you get a few configuration provider defaults out of the box: JSON, Command-Line, and Environment Variables. Additionally, when in local development mode, .NET Core has access to User Secrets. It’s important to think ahead to production and choose a strategy that looks to the future.

How Congifuration Works In .NET Core

At its simplest, .NET Core’s configuration system is entirely key-value based. Each registered configuration provider generates and pushes a collection of key-value pairs into one configuration instance, and developers can access those values via an IConfiguration interface.

Keys in .NET Core can also convey hierarchy through the use of a : character. Let’s take a look at some examples of keys you may see in your application.

Enabled
Copyright:Date
Copyright:Company
Employees:0
Employees:1

In the example keys above, the Enabled key is a top-level key, while the Copyright keys denote an object of values. Finally, the Employees key uses indices and indicates that you may be dealing with an array of values. Note, that configuration in .NET Core is additive. That means as you stack configuration providers, they can add new keys, or even overwrite existing ones.

One thing I like about .NET Core’s configuration model is that you can mix and match different file types. Furthermore, you can load similar file types based on your needs.

Default Configuration Sources

Out of the box, .NET Core applications include four registered configuration providers, but only if you call CreateDefaultBuilder in your Program.cs file.

public static IHostBuilder CreateHostBuilder(string[] args) =>
   Host.CreateDefaultBuilder(args)
      ConfigureWebHostDefaults(webBuilder =>
      {
         webBuilder.UseStartup<Startup>();
      });

Let’s take a look at these providers.

JSON

JSON is the most potent default configuration provider. It allows developers to express complex hierarchical configuration structures. An added benefit to using JSON is that most configuration values can be mapped one-to-one to a C# object.

A new application has appSettings.json.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

It also has appSetting.Development.json.

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Remember, how I said the configuration system is additive? These two files combine to produce multiple key-values that are used by the .NET Core application.

Environment Variables

Environment variables are my personal favorite. This method of configuration can be most helpful when you need to set values based on the hosting platform. The most common environment variable developers encounter is the ASPNETCORE_ENVIRONMENT which has a default value of Production.

If you’re an Azure user, this is the primary way the cloud platform pushes configuration into your hosted apps.

Command Line Arguments

Executed apps can take command-line arguments, and it couldn’t be easier for developers.

dotnet ./myapp.dll "awesome"

You’ve probably noticed that the main method in Program.cs takes a string[]. These parameters are the command-line arguments.

public class Program
{
    // command line arguments here
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    // command line arguments here too!
    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

This configuration provider isn’t as useful for deployed applications but can make working with a .NET Core app easier during local development. That said, I would rarely use this feature myself during my development and opt for environment variables or local configuration files.

XML

JSON may have overshadowed XML, but when it comes to sheer data density and data clarity, it can still be an excellent choice for configuration. Microsoft offers an XML Configuration provider for folks who prefer the comforting nature of open and close tags. Let’s take a look at the following XML file and how it ends up in our configuration instance.

After installing the NuGet package of Microsoft.Extensions.Configuration.Xml, we need to register the XML configuration provider.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration(config => { config.AddXmlFile("test.xml"); })
        .UseStartup<Startup>();

Let’s take a look at the file we’ll be loading.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <test enable="true" />
    <another>value</another>
</configuration>

xml configuration results

It works, but we only get the complete key-value pair of another:value. It turns out this XML provider, does not support attributes.

YAML

YAML, likely the most maligned file format due to its ability to make a developer’s job difficult. It is a white-space significant format that can be challenging to debug because developers are known to have overactive thumbs and love smashing the space bar.

I think YAML is an excellent choice for simple configuration scenarios. If you are a modern blogger, then you’re likely already familiar with YAML. It is the format for some of the most popular static site generators.

Let’s setup YAML in our project using Andrew Lock’s YAML configuration provider.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration(config =>
        {
            config.AddYamlFile("test.yaml");
        })
        .UseStartup<Startup>();

The configuration file is a plain old YAML file.

yaml_enabled: true
yaml_another: value

If we look at the keys, it looks very similar to our YAML file.

yaml_enabled: true
yaml_another: value

yaml configuration results

Note, I’ve noticed this package can only read scalar based YAML configurations. That means nested YAML configuration files may not work as expected. It’s best to stick to key-value YAML configurations.

Consul

If you’re building systems that need to share configuration across multiple instances of the same application, you may want to consider a tool like Consul. HashiCorp develops this software, and describes Consul as follows:

Consul is a service networking solution to connect and secure services across any runtime platform and public or private cloud HashiCorp

A blog post written by Winton, the creators of the configuration provider, explains the benefits well. In summary, if you want to centralize your configuration with the benefits of auditing, versioning, security, and real-time updates, then Consul is a great option. For developers, they can run Consul in a docker container locally, which should make working with this dependency much easier.

I love that the package enables the Consul service to communicate back to your application and reload configuration in real-time. Consul also has a UI that can is viewable at the demo site.

Azure Key Vault

If you’re in the .NET ecosystem, then you have undoubtedly heard of Windows Azure. The cloud provider has an onslaught of features, and one of those features is Azure Key Vault. The documentation site explains the purpose of the service as follows:

Cloud applications and services use cryptographic keys and secrets to help keep information secure. Azure Key Vault safeguards these keys and secrets. When you use Key Vault, you can encrypt authentication keys, storage account keys, data encryption keys, .pfx files, and passwords by using keys that are protected by hardware security modules (HSMs). Microsoft

In the case of our applications, we want to use Azure Key Vault as a mechanism for secret management. You can use Azure Key Vault as a direct configuration provider in your .NET Core applications. Additionally, you can also connect a key vault instance to your Azure App Service. By creating a connection between the two services, secrets are injected as Environment Variables and are accessible at run time.

I generally like to keep my ASP.NET Core applications as simple as possible with the fewest direct dependencies. I like Azure Key Vault as an environmental implementation of the Windows Azure ecosystem. Less so as a direct implementation in my codebase.

Conclusion

When starting a .NET Core application, your choice of configuration can be overwhelming, and it certainly was for me. Before choosing any new configuration providers, take advantage of the default ones. They are generally excellent for most use cases. Situations that you may outgrow them, generally have to do with scaling out your application to multiple instances. If you need to centralize and share configuration, I recommend Azure Key Vault and Consul. Ultimately, if you need a more bespoke approach, it can be straightforward to write a custom configuration provider. A few of the configuration options on this list started as an idea from the community.

Remember, all applications evolve, and so should your configuration approach. Consider your needs and the needs of your team. The configuration is a means to communicate the intent and behavior of your application. When done well, the success of your project is assured just that much more.

Good Luck!