Skip to main content
  1. Posts/

Configuration overrides in .NET: be careful with Collections and Objects

·808 words·4 mins
.NET Development Configuration IOptions Configuration Overrides
Alexey Gnetko
Author
Alexey Gnetko
Tech enthusiast. Curious about modern approaches in software engineering

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.

Further reading
#