IConfiguration
, especially when used in conjunction with
IOptions, is a standard and robust pattern for accessing configuration in .NET.
The configuration can be provided in various formats (JSON, XML, INI) and from multiple sources (files, environment variables, and remote configuration storages).
It can also be overridden for specific environments or purposes (e.g., Development, Production). By overriding the default settings with environment-specific ones, you can ensure that your application behaves correctly in each environment.
Although IConfiguration
is well-documented and intuitive, one aspect is often overlooked:
IConfiguration
object stores all configurations and overrides in a key-value map of type IDictionary<string, string?>
.
So even if you define your configuration in JSON or XML, it will still be converted to a flat dictionary. This requires extra caution when working with collections and objects in your configuration, as it can be tricky to override their values.
Why should you care? #
As mentioned earlier, all configurations consumed by an IConfiguration
object are stored as key-value pairs in IDictionary<string, string?>
. See Data
defined in
ConfigurationProvider.cs:
/// <summary>
/// The configuration key-value pairs for this provider.
/// </summary>
protected IDictionary<string, string?> Data { get; set; }
While it works well for environment variables and INI files, JSON and XML formats allow using nested objects and collections. Any configuration format (JSON, XML, INI, or custom) provided to .NET via ConfigurationProvider
will be converted to IDictionary<string, string?>
and stored in the Data
property.
This means that all JSON/XML objects (including collections) are iterated and transformed into a flat dictionary with string
or null
values.
Consider this appsettings.json
example:
{
"WeekSettings": {
"StartDay": "Sunday",
"WorkDays": [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
}
It will be converted to IDictionary<string, string?> Data
:
WeekSettings:StartDay = Sunday
WeekSettings:WorkDays:0 = Monday
WeekSettings:WorkDays:1 = Tuesday
WeekSettings:WorkDays:2 = Wednesday
WeekSettings:WorkDays:3 = Thursday
WeekSettings:WorkDays:4 = Friday
What implications does it have for developers?
If you want to override WorkDays
from appsettings.json
in Development
and set it just to 3 days (let’s say to Monday, Wednesday and Friday), we can try to use appsettings.Development.json
:
{
// ⚠️ caution: this override won't work well, please read further
"WeekSettings": {
"StartDay": "Monday",
"WorkDays": [
"Monday",
"Wednesday",
"Friday"
]
}
}
Looks cool, but it doesn’t work as expected. You’ll get the following Data
dictionary from appsettings.Development.json
:
WeekSettings:StartDay = Monday
WeekSettings:WorkDays:0 = Monday
WeekSettings:WorkDays:1 = Wednesday
WeekSettings:WorkDays:2 = Friday
And the result of merging appsettings.json
and appsettings.Development.json
dictionaries will be:
WeekSettings:StartDay = Monday
WeekSettings:WorkDays:0 = Monday
WeekSettings:WorkDays:1 = Wednesday
WeekSettings:WorkDays:2 = Friday
WeekSettings:WorkDays:3 = Thursday // oops, Thursday is still here
WeekSettings:WorkDays:4 = Friday // second Friday? 😲
The final merged JSON is below. You’re lucky if you notice this issue during review or in Development
environment. But if it sneaks into Production
, you’ll have a good lesson learned.
{
"WeekSettings": {
"StartDay": "Monday",
"WorkDays": [ "Monday", "Wednesday", "Friday", "Thursday", "Friday"]
}
}
Here’s a table for a better picture of the Development environment configuration:
. | |||
---|---|---|---|
Configuration key | appsettings.json value |
appsettings.Development.json value |
Final configuration value |
WeekSettings:StartDay | Sunday | Monday | Monday |
WeekSettings:WorkDays:0 | Monday | Monday | Monday |
WeekSettings:WorkDays:1 | Tuesday | Wednesday | Wednesday |
WeekSettings:WorkDays:2 | Wednesday | Friday | Friday |
WeekSettings:WorkDays:3 | Thursday | - | Thursday |
WeekSettings:WorkDays:4 | Friday | - | Friday |
How to deal with configuration overrides in .NET #
While configuration overrides can be incredibly useful, they can also lead to unexpected behavior if not managed properly.
Below are some recommendations on how to handle it better.
Design it well #
First of all, be aware that .NET merges configuration overrides into a flat dictionary (key-value map). Knowing this will help you to carefully build the right configuration structure and avoid issues.
Keep a minimal default configuration #
Having a minimal default configuration in appsettings.json
and specifying environment-specific overrides in appsettings.{environment}.json
can simplify your configuration management. By only including the minimal default configuration you can reduce the amount of configuration that needs to be overridden.
Use key-value maps (objects with fields) instead of collections #
For example, instead of the WorkDays
collection from the example above, you can use an object:
{
"WorkDays": {
"Monday": true,
"Tuesday": false,
"Wednesday": true
// and so on
}
}
Although this approach is less elegant, it is explicit and makes it easier to override the configuration.
Override objects with objects of the same type #
Override objects field-by-field and avoid overriding objects with another object or with primitive types (e.g., int
or bool
). This may lead to issues that are difficult to debug.
// Original configuration
{
"Settings": {
"Threshold": 10,
"Enabled": true
}
}
// Override configuration
{
"Settings": {
"Threshold": 5
// Keep the original value of "Enabled": true
}
}
Override collections with larger ones #
If you need to override a collection, ensure that you replace it with a collection containing larger number of elements. This can help prevent issues with two Fridays as in the example above.