Reputation: 199
I have a base configuration file eg.
appsettings.json
{
"Values": {
"Test": ["one", "two"]
}
}
and
appsettings.dev.json
{
"Values": {
"Test": ["three"]
}
}
and after transforming, the array would be
["three", "two"]
How do I make sure the transformed array is shrunk to a smaller number of elements rather than each element changing individually?
Upvotes: 7
Views: 3970
Reputation: 92
@Matt in my opinion it's just an unnecessary logic. Flow 'KISS'.
appsettings.json should contain only common settings. If your production mode or development mode must have some same values in one key, just duplicate their. Like appsettings.Development.json
"Values": {
"Test": ["one", "two"]
}
and appsettings.Production.json
"Values": {
"Test": ["one", "two","three"]
}
And if you need same values for both modes you should put it in appsettings.json.
"SomeValues": {
"Test": ["1", "2","3"]
}
In final settings you will have for production
"SomeValues": {
"Test": ["1", "2","3"]
},
"Values": {
"Test": ["one", "two","three"]
}
and for development
"SomeValues": {
"Test": ["1", "2","3"]
},
"Values": {
"Test": ["one", "two"]
}
anyway If previous answer solve your problem it's ok, it's just my opinion. Thanks)
Upvotes: 3
Reputation: 92
I recommend use appsettings.Development.json and appsettings.Production.json to separate environments. And keep common settings in appsettings.json for both environments.
Just rename your appsettings.dev.json to appsettings.Development.json. Add Stage or Prodaction mode of appsettings.{#mode}.json. And modify ConfigurationBuilder in Startup.cs.
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
I think it's more common practice and could save your time from unnecessary logic of merging
Upvotes: 2
Reputation: 31282
To understand the cause of such 'strange' behavior for overridden array settings you need to understand how those settings are stored inside configuration providers.
The reality is that all loaded settings are stored in dictionaries, own for each configuration provider. Keys are built from setting paths where nested sections are delimited with a colon.
Array settings are stored in the same dictionary with an index in setting path (:0
, :1
, ...).
For configuration you described you will have 2 configuration providers with following sets of settings:
provider1[Values:Test:0] = "one"
provider1[Values:Test:1] = "two"
and
provider2[Values:Test:0] = "three"
Now it's clear why the final value of array setting is ["three", "two"]
. Values:Test:0
from the second provider overrides the same setting from the first provider, and Values:Test:1
is left untouched.
Unfortunately, there is now a built-in possibility to overcome this problem. Fortunately, .net core configuration model is flexible enough for adjusting this behavior for your needs.
Idea is the following:
IConfigurationProvider.GetChildKeys()
method recursively for this purpose. See GetProviderKeys()
in below snippet.null
value.For convenience you could wrap all this logic into extension method on IConfigurationRoot
.
Here is a working sample:
public static class ConfigurationRootExtensions
{
private static readonly Regex ArrayKeyRegex = new Regex("^(.+):\\d+$", RegexOptions.Compiled);
public static IConfigurationRoot FixOverridenArrays(this IConfigurationRoot configurationRoot)
{
HashSet<string> knownArrayKeys = new HashSet<string>();
foreach (IConfigurationProvider provider in configurationRoot.Providers.Reverse())
{
HashSet<string> currProviderArrayKeys = new HashSet<string>();
foreach (var key in GetProviderKeys(provider, null).Reverse())
{
// Is this an array value?
var match = ArrayKeyRegex.Match(key);
if (match.Success)
{
var arrayKey = match.Groups[1].Value;
// Some provider overrides this array.
// Suppressing the value.
if (knownArrayKeys.Contains(arrayKey))
{
provider.Set(key, null);
}
else
{
currProviderArrayKeys.Add(arrayKey);
}
}
}
foreach (var key in currProviderArrayKeys)
{
knownArrayKeys.Add(key);
}
}
return configurationRoot;
}
private static IEnumerable<string> GetProviderKeys(IConfigurationProvider provider,
string parentPath)
{
var prefix = parentPath == null
? string.Empty
: parentPath + ConfigurationPath.KeyDelimiter;
List<string> keys = new List<string>();
var childKeys = provider.GetChildKeys(Enumerable.Empty<string>(), parentPath)
.Distinct()
.Select(k => prefix + k).ToList();
keys.AddRange(childKeys);
foreach (var key in childKeys)
{
keys.AddRange(GetProviderKeys(provider, key));
}
return keys;
}
}
The last thing is to call it when building the configuration:
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("AppSettings.json")
.AddJsonFile("appsettings.dev.json");
var configuration = configurationBuilder.Build();
configuration.FixOverridenArrays();
Upvotes: 7