Environment Specific Configuration in DotNet Core
It's common to have different application configurations per environment (e.g: Dev, QA, Production) allowing us to switch between them at each stage.
In older Web Applications this was usually done with Web Config Transformations or Razor files, however the new ConfigurationBuilder
available in DotNet Core makes it relatively straight forward to use multiple files, building up & overridding a set of config keys.
Below is a quick guide on how to do this. All the code in this article can be found here: https://github.com/lukemerrett/EnvironmentConfigSample
The Config Files
Let's stary with 3 configuration files, simulating our environment. Firstly, appsettings.json
:
{
"Hosts": {
"TestsEnabled": true,
"Tenant": "CA",
"FirstApi": "http://ca.development.api1.localhost",
"SecondApi": "http://ca.development.api2.localhost"
}
}
Then we have appsettings.Staging.CA.json
:
{
"Hosts": {
"Tenant": "CA",
"FirstApi": "http://ca.staging.api1.localhost",
"SecondApi": "http://ca.staging.api2.localhost"
}
}
And finally we have appsettings.Staging.UK.json
:
{
"Hosts": {
"Tenant": "UK",
"FirstApi": "http://uk.staging.api1.localhost",
"SecondApi": "http://uk.staging.api2.localhost"
}
}
What's important to note here is that we're not overridding every property in the app settings for Staging CA and UK. The TestsEnabled
property is left the same, so will not be overridden.
This allows us to build a set of base config files, and only override the behaviour we need to be environment specific later on.
Model
Next we have a basic DTO class representing the configuration:
namespace EnvironmentConfig.Model
{
public class HostsConfiguration
{
public bool TestsEnabled { get; set; }
public string Tenant { get; set; }
public string FirstApi { get; set; }
public string SecondApi { get; set; }
}
}
NuGet References
To use the ConfigurationBuilder
in this context we need to reference 2 NuGet packages:
- Microsoft.Extensions.Configuration.Json
- Provides access to json based configuration files
- Microsoft.Extensions.Configuration.Binder
- Deserialises the configuration into our model
Configuration Loader
Now we have that set up, the last piece of the puzzle is the loader itself. This controls which settings files to load into the configuration builder:
using EnvironmentConfig.Model;
using Microsoft.Extensions.Configuration;
using System.Globalization;
using System.IO;
namespace EnvironmentConfig
{
public class ConfigurationLoader
{
private const string DefaultSettingsFile = "./Config/appsettings.json";
/// <summary>
/// Loads the configuration for the given environment and tenant.
/// If no environment or tenant is provided, then the default local config is loaded.
/// </summary>
/// <param name="environment">The environment to point to.</param>
/// <param name="tenant">The country to target.</param>
/// <returns>The relevant configuration for .</returns>
public HostsConfiguration Load(string environment = "", string tenant = "")
{
string targetJsonFile = DefaultSettingsFile;
if (!string.IsNullOrWhiteSpace(environment) && !string.IsNullOrWhiteSpace(tenant))
{
// Override default config with environment specific config
environment = FormatEnvironment(environment);
tenant = FormatTenant(tenant);
targetJsonFile = $"./Config/appsettings.{environment}.{tenant}.json";
}
// Loads the default config, then overrides matching keys
// with the environment specific version
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(DefaultSettingsFile)
.AddJsonFile(targetJsonFile)
.Build();
var gitHubConfiguration = new HostsConfiguration();
// Deserialises the config into the HostsConfiguration object
configuration.GetSection("Hosts").Bind(gitHubConfiguration);
return gitHubConfiguration;
}
private string FormatEnvironment(string environment)
{
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(environment);
}
private string FormatTenant(string tenant)
{
return tenant.ToUpper();
}
}
}
Using The Loader
Now that's setup we can use the loader to pull in the correct configuration for our environment:
var configLoader = new ConfigurationLoader();
var configuration = configLoader.Load("Staging", "UK");
Console.WriteLine($"Tests Enabled: {configuration.TestsEnabled}");
Console.WriteLine($"Tenant: {configuration.Tenant}");
Console.WriteLine($"First API: {configuration.FirstApi}");
Console.WriteLine($"Second API: {configuration.SecondApi}");
The output from this will be:
Tests Enabled: true
Tenant: UK
First API: http://uk.staging.api1.localhost
Second API: http://uk.staging.api2.localhost
And that's how to load environment specific configuration in DotNet Core.
Image credit: Tooth & Tail - Wikimedia