In .NET, the application configuration is stored in
appsettings.json file. This article explains how can config transformations be applied to configurations in
appsettings.json in .NET applications.
Why Config Transformations?
The configuration files contain settings which are required for your code to execute, and some of them might have different values on different environments.
For ex. let’s say, your application interacts with a SQL Server database. So, your application would need to know the connection string for connecting to it. This connection string would have some value for dev environment and another value for test environment and some third value for the production environment.
The config transformations help to transform the values – automatically during the build. So, once you setup the config transformations, your build pipelines will automatically update the config file with environment specific values, saving you from the tedious job of writing any code to update the config files (or even worst, modifying configs manually after deployment).
.NET Framework – Web.Config Transformations
Traditionally, .NET Framework uses web.config (or app.config) XML configuration files. You might already be aware of a nuget package – SlowCheetah. If not, you can read about it here on GitHub. The readme file explains how the build time transformations work.
How does .NET loads configurations ?
The .NET uses
appsettings.json as the default configuration file, instead of web.config. The configurations are in the JSON format. The .NET uses configuration providers to load configuration settings from various sources.
So, let’s quickly have look at what are configuration providers. Later we will also quickly have a look at how those providers are called via
CreateDefaultBuilder method. That will ultimately help us to know more about how the transformations work with
What are Configuration Providers ?
There are different types of configuration providers, which are used for loading configurations from various sources.
- Azure Key Vault configuration provider to load configurations from key vault
- Azure App configuration provider to load configuration from Azure app configurations
- Command-line configuration provider to load settings from command line
- Environment Variables configuration provider to load settings from environment variables
- File configuration provider to load settings from JSON, INI or XML files
- User secrets to load settings from a file present in user profile directory
- Memory configuration provider to load settings from in-memory collections
- Key-per-file configuration provider where key is file name and value is its contents
- Custom configuration provider to load settings from any custom source (e.g. CosmosDB, MongoDB, SqlServer, InMemory database, etc.)
When the framework builds IHostBuilder instance using CreateDefaultBuilder method, it loads the configurations using some of these providers.
CreateDefaultBuilder provides default configuration for the app in the following order:
- ChainedConfigurationProvider : Adds an existing
IConfigurationas a source. In the default configuration case, adds the host configuration and setting it as the first source for the app configuration.
- appsettings.json using the JSON configuration provider.
Environment.json using the JSON configuration provider. For example, appsettings.Production.json and appsettings.Development.json.
- App secrets when the app runs in the
- Environment variables using the Environment Variables configuration provider.
- Command-line arguments using the Command-line configuration provider.
Summary of how it works…
So, first, the
appsettings.json is loaded. Then the values from
appsettings.environment-name.json are loaded. It also does other things like loading values from app secrets, or applying command line inputs, but for the sake of simplicity, I have tried to keep focus on
I already have discussed in the previous blog post that .NET uses
ASPNETCORE_ENVIRONMENT variable to identify the logical environment. So, if you have multiple
appsettings.environment-name.json files, the actual file which overrides
appsettings.json settings would be decided by the environment variable.
Let’s setup transformation
We now have enough information to get started on this task. Let’s create a
.NET Core MVC web application in Visual Studio.
appsettings.json file, add a new json property, Mode, which is set to “default“. Also add an array of JSON objects, MailFeature, a hypothetical mail feature which uses this configuration to set the email from addresses and subjects based on type of emails.
appsettings.json should have contents as shown below.
Then, let’s modify
Index action from
HomeController.cs to populate the view data as shown below.
Next, let’s modify
Index.cshtml file to show the Mode value from the
ViewData collection on the header, as shown in below code snippet.
Now, if you exapand the
appsettings.json, it should already show you an environment specific JSON file with name
appsettings.Development.json. Let’s add two more JSON files:
NOTE: The CreateDefaultBuilder method performs case insensitive matching for finding environment specific configuration file. So, if the value of
test, it would still be correctly able to apply transformations from
Override a setting from JSON object
For overriding the default Mode from appsettings.json, add the Mode property to environment specific appsettings file and set it to the respective environment name, as shown in below snippet.
As you can see, you can create the JSON structure of the object and specify only those properties which you want to override in the environment specific files.
The disadvantage that one may say is – if a configuration property is nested at third level, then those levels need to be present in the environment specific JSON file.
Override a setting from JSON array
The obvious question that was bothering me was – how to handle JSON object arrays from appsettings.json ?
For example, we have a JSON array MailFeature. Let’s say we want to put environment name in subject if the environment is
Test, but obviously environment name should not be present in subject on
Would I need to retype everything from the array ? If not, then how will it identify which subject belongs to which json object from the array?
So, just out of curiosity, I modified the
appsettings.Development.json as shown in below snippet to understand how it works.
As I expected, the code transformed the subjects appropriately. This might have happend because the provider reads the values from array in the sequence in which it appears in the file.
So, yeah, like JSON objects, you can transform JSON arrays too. The only thing to take care is the item to be transformed should be present at the same array index in both the files.
Everything is (Key, Value) Pair
I tried to dig this further to see how the configuration is internally handled by .NET.
So, I put a breakpoint in Index action. When execution flow reached there, I used immediate window to execute
configuration.AsEnumerable().ToList(). This returned below result:
So, internally everything is
IEnumerable<KeyValuePair<string string>>. The key is formed by appending all previous JSON keys separated by colon (:) characther.
If everything is key value pair, then the transformation files can also be written as shown below. It might not provide any advantage for JSON objects which are at topmost level.
For transforming only a few properties from either JSON arrays or deep nested objects, it might save some key strokes for you.
For verifying, you can quickly run the sample from Visual Studio. Every time you run, change the
ASPNETCORE_ENVIRONMENT value from
launchSetting.json to any value from “Development”, “Test”, “Staging”, and “Production”. The home page should show the header text from the environment specific JSON file.
So, we have seen how easy it is to setup the config transformations in .NET. You do not need any third party packages for setting it up. Further more, there are many possibilities.
For example, only if you wish, you can write your own code to create the IHostBuilder instance, and that code can decide how the transformation works. That custom code can use something else like secrets or key vault or any other configuration store to override the values from
I hope you found this information useful. Let me know your thoughts.