Understanding Array Configuration Overrides in ASP.NET Core
Written on
Chapter 1: Introduction to ASP.NET Core Configuration
ASP.NET Core employs configuration providers to gather key-value pairs from diverse sources. The official documentation elaborates on various use cases, offering in-depth insights into configuration management.
Recently, while working with JSON arrays in the appsettings.json file of an ASP.NET Core project, I encountered an unexpected behavior. Unlike typical JSON objects, arrays did not function in the same manner when overridden in environment-specific appsettings files. As I delved deeper, I discovered numerous discussions on Stack Overflow and GitHub concerning this intriguing aspect of configuration inheritance for arrays. This article aims to clarify the "problem" so you can navigate it more easily.
Section 1.1: The Configuration Challenge
A fundamental aspect of ASP.NET Core configuration is that when identical keys are present across various sources, the provider added last will supersede earlier ones. For instance, a connection string defined in appsettings.json will prevail in the development environment unless a different value is specified in appsettings.Development.json. Conversely, if a connection string appears in appsettings.Production.json, it will replace the value in appsettings.json during production.
Typically, we place general configuration data in the appsettings.json file while storing environment-specific settings in appsettings.{Environment}.json files or environment variables. This structure allows the application to adapt its settings based on the runtime environment upon startup.
However, arrays do not follow this pattern precisely. For demonstration purposes, I created a Console app that is available in my GitHub repository.
The demo Console application employs the default HostBuilder. When executed in development mode, it loads configurations first from appsettings.json, followed by appsettings.Development.json. We define our configuration object through a class named AppSettings as shown below.
public class AppSettings
{
public string[] MyArray0 { get; set; }
public string[] MyArray1 { get; set; }
public string[] MyArray2 { get; set; }
}
The AppSettings class includes three properties, each representing an array of strings. The subsequent lines illustrate how the Console app binds the configurations from the JSON files and displays their values. In this demonstration, we will conduct experiments with these three arrays, assigning different values and lengths across each JSON file. Below are the two JSON files we utilized.
You might expect that the final values for the three arrays would be sourced solely from appsettings.Development.json, as it is the last file loaded. However, this assumption is partially correct. The final output is as follows:
MyArray0: value1-override, value2-override
MyArray1: value1-override, value2-override
MyArray2: value2-override, value1
Section 1.2: Understanding the Unexpected Results
The first two arrays yield the anticipated values from appsettings.Development.json, while MyArray2 exhibits values from both appsettings files, challenging our expectations.
What transpired? The official ASP.NET Core configuration documentation indicates that the ConfigurationBinder utilizes array indices in configuration keys when binding arrays to objects. By examining the Configuration object with the Visual Studio debugger, we can observe that the JSON provider delivers the following configuration data from appsettings.Development.json.
Array elements are indexed by their positions; for example, the second element in MyArray1 is identified as AppSettings:MyArray1:1. This means that binding two JSON formats (one structured as an object and the other as an array) will yield identical values.
Consequently, if you utilize the JSON array format in the settings file, the ConfigurationBinder assigns each element a unique key based on its index. In this case, MyArray2 in appsettings.Development.json contains a single element, leading the ConfigurationBinder to overwrite the configuration data with the key AppSettings:MyArray2:0 and retain the other value from appsettings.json with the key AppSettings:MyArray2:1. Thus, MyArray2 ends up with two elements.