Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStephen Halter <halter73@gmail.com>2021-07-13 20:26:42 +0300
committerGitHub <noreply@github.com>2021-07-13 20:26:42 +0300
commita490a3417adb78dbc36891624e67720ebdca919f (patch)
tree62d3c9d687509a96b6e83c8336915a58cdd3f0c5 /src
parent9a9b105bfa8e6803534333a096cb5af8490b5340 (diff)
Add ConfigurationManager(#55338)
* ConfigurationManager : IConfigurationRoot, IConfigurationBuilder
Diffstat (limited to 'src')
-rw-r--r--src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs15
-rw-r--r--src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs353
-rw-r--r--src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs56
-rw-r--r--src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationManagerTest.cs1201
4 files changed, 1599 insertions, 26 deletions
diff --git a/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs b/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs
index c3cc43f7eff..feef64fc2dd 100644
--- a/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs
+++ b/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs
@@ -28,6 +28,21 @@ namespace Microsoft.Extensions.Configuration
public bool ShouldDisposeConfiguration { get { throw null; } set { } }
public Microsoft.Extensions.Configuration.IConfigurationProvider Build(Microsoft.Extensions.Configuration.IConfigurationBuilder builder) { throw null; }
}
+ public sealed partial class ConfigurationManager : Microsoft.Extensions.Configuration.IConfigurationBuilder, Microsoft.Extensions.Configuration.IConfigurationRoot, System.IDisposable
+ {
+ public ConfigurationManager() { }
+ public string this[string key] { get { throw null; } set { throw null; } }
+ public IConfigurationSection GetSection(string key) { throw null; }
+ public System.Collections.Generic.IEnumerable<IConfigurationSection> GetChildren() { throw null; }
+ public void Dispose() { throw null; }
+ System.Collections.Generic.IDictionary<string, object> IConfigurationBuilder.Properties { get { throw null; } }
+ System.Collections.Generic.IList<Microsoft.Extensions.Configuration.IConfigurationSource> IConfigurationBuilder.Sources { get { throw null; } }
+ Microsoft.Extensions.Configuration.IConfigurationBuilder IConfigurationBuilder.Add(Microsoft.Extensions.Configuration.IConfigurationSource source) { throw null; }
+ Microsoft.Extensions.Configuration.IConfigurationRoot IConfigurationBuilder.Build() { throw null; }
+ System.Collections.Generic.IEnumerable<IConfigurationProvider> IConfigurationRoot.Providers { get { throw null; } }
+ void IConfigurationRoot.Reload() { throw null; }
+ Primitives.IChangeToken IConfiguration.GetReloadToken() { throw null; }
+ }
public partial class ConfigurationBuilder : Microsoft.Extensions.Configuration.IConfigurationBuilder
{
public ConfigurationBuilder() { }
diff --git a/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs
new file mode 100644
index 00000000000..9578f3c334a
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs
@@ -0,0 +1,353 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Extensions.Configuration
+{
+ /// <summary>
+ /// Configuration is mutable configuration object. It is both an <see cref="IConfigurationBuilder"/> and an <see cref="IConfigurationRoot"/>.
+ /// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen.
+ /// </summary>
+ public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable
+ {
+ private readonly ConfigurationSources _sources;
+ private readonly ConfigurationBuilderProperties _properties;
+
+ private readonly object _providerLock = new();
+ private readonly List<IConfigurationProvider> _providers = new();
+ private readonly List<IDisposable> _changeTokenRegistrations = new();
+ private ConfigurationReloadToken _changeToken = new();
+
+ /// <summary>
+ /// Creates an empty mutable configuration object that is both an <see cref="IConfigurationBuilder"/> and an <see cref="IConfigurationRoot"/>.
+ /// </summary>
+ public ConfigurationManager()
+ {
+ _sources = new ConfigurationSources(this);
+ _properties = new ConfigurationBuilderProperties(this);
+
+ // Make sure there's some default storage since there are no default providers.
+ this.AddInMemoryCollection();
+
+ AddSource(_sources[0]);
+ }
+
+ /// <inheritdoc/>
+ public string this[string key]
+ {
+ get
+ {
+ lock (_providerLock)
+ {
+ return ConfigurationRoot.GetConfiguration(_providers, key);
+ }
+ }
+ set
+ {
+ lock (_providerLock)
+ {
+ ConfigurationRoot.SetConfiguration(_providers, key, value);
+ }
+ }
+ }
+
+ /// <inheritdoc/>
+ public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key);
+
+ /// <inheritdoc/>
+ public IEnumerable<IConfigurationSection> GetChildren()
+ {
+ lock (_providerLock)
+ {
+ // ToList() to eagerly evaluate inside lock.
+ return this.GetChildrenImplementation(null).ToList();
+ }
+ }
+
+ IDictionary<string, object> IConfigurationBuilder.Properties => _properties;
+
+ IList<IConfigurationSource> IConfigurationBuilder.Sources => _sources;
+
+ IEnumerable<IConfigurationProvider> IConfigurationRoot.Providers
+ {
+ get
+ {
+ lock (_providerLock)
+ {
+ return new List<IConfigurationProvider>(_providers);
+ }
+ }
+ }
+
+ /// <inheritdoc/>
+ public void Dispose()
+ {
+ lock (_providerLock)
+ {
+ DisposeRegistrationsAndProvidersUnsynchronized();
+ }
+ }
+
+ IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source)
+ {
+ _sources.Add(source ?? throw new ArgumentNullException(nameof(source)));
+ return this;
+ }
+
+ IConfigurationRoot IConfigurationBuilder.Build() => this;
+
+ IChangeToken IConfiguration.GetReloadToken() => _changeToken;
+
+ void IConfigurationRoot.Reload()
+ {
+ lock (_providerLock)
+ {
+ foreach (var provider in _providers)
+ {
+ provider.Load();
+ }
+ }
+
+ RaiseChanged();
+ }
+
+ private void RaiseChanged()
+ {
+ var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
+ previousToken.OnReload();
+ }
+
+ // Don't rebuild and reload all providers in the common case when a source is simply added to the IList.
+ private void AddSource(IConfigurationSource source)
+ {
+ lock (_providerLock)
+ {
+ var provider = source.Build(this);
+ _providers.Add(provider);
+
+ provider.Load();
+ _changeTokenRegistrations.Add(ChangeToken.OnChange(() => provider.GetReloadToken(), () => RaiseChanged()));
+ }
+
+ RaiseChanged();
+ }
+
+ // Something other than Add was called on IConfigurationBuilder.Sources or IConfigurationBuilder.Properties has changed.
+ private void ReloadSources()
+ {
+ lock (_providerLock)
+ {
+ DisposeRegistrationsAndProvidersUnsynchronized();
+
+ _changeTokenRegistrations.Clear();
+ _providers.Clear();
+
+ foreach (var source in _sources)
+ {
+ _providers.Add(source.Build(this));
+ }
+
+ foreach (var p in _providers)
+ {
+ p.Load();
+ _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));
+ }
+ }
+
+ RaiseChanged();
+ }
+
+ private void DisposeRegistrationsAndProvidersUnsynchronized()
+ {
+ // dispose change token registrations
+ foreach (var registration in _changeTokenRegistrations)
+ {
+ registration.Dispose();
+ }
+
+ // dispose providers
+ foreach (var provider in _providers)
+ {
+ (provider as IDisposable)?.Dispose();
+ }
+ }
+
+ private class ConfigurationSources : IList<IConfigurationSource>
+ {
+ private readonly List<IConfigurationSource> _sources = new();
+ private readonly ConfigurationManager _config;
+
+ public ConfigurationSources(ConfigurationManager config)
+ {
+ _config = config;
+ }
+
+ public IConfigurationSource this[int index]
+ {
+ get => _sources[index];
+ set
+ {
+ _sources[index] = value;
+ _config.ReloadSources();
+ }
+ }
+
+ public int Count => _sources.Count;
+
+ public bool IsReadOnly => false;
+
+ public void Add(IConfigurationSource source)
+ {
+ _sources.Add(source);
+ _config.AddSource(source);
+ }
+
+ public void Clear()
+ {
+ _sources.Clear();
+ _config.ReloadSources();
+ }
+
+ public bool Contains(IConfigurationSource source)
+ {
+ return _sources.Contains(source);
+ }
+
+ public void CopyTo(IConfigurationSource[] array, int arrayIndex)
+ {
+ _sources.CopyTo(array, arrayIndex);
+ }
+
+ public IEnumerator<IConfigurationSource> GetEnumerator()
+ {
+ return _sources.GetEnumerator();
+ }
+
+ public int IndexOf(IConfigurationSource source)
+ {
+ return _sources.IndexOf(source);
+ }
+
+ public void Insert(int index, IConfigurationSource source)
+ {
+ _sources.Insert(index, source);
+ _config.ReloadSources();
+ }
+
+ public bool Remove(IConfigurationSource source)
+ {
+ var removed = _sources.Remove(source);
+ _config.ReloadSources();
+ return removed;
+ }
+
+ public void RemoveAt(int index)
+ {
+ _sources.RemoveAt(index);
+ _config.ReloadSources();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ private class ConfigurationBuilderProperties : IDictionary<string, object>
+ {
+ private readonly Dictionary<string, object> _properties = new();
+ private readonly ConfigurationManager _config;
+
+ public ConfigurationBuilderProperties(ConfigurationManager config)
+ {
+ _config = config;
+ }
+
+ public object this[string key]
+ {
+ get => _properties[key];
+ set
+ {
+ _properties[key] = value;
+ _config.ReloadSources();
+ }
+ }
+
+ public ICollection<string> Keys => _properties.Keys;
+
+ public ICollection<object> Values => _properties.Values;
+
+ public int Count => _properties.Count;
+
+ public bool IsReadOnly => false;
+
+ public void Add(string key, object value)
+ {
+ _properties.Add(key, value);
+ _config.ReloadSources();
+ }
+
+ public void Add(KeyValuePair<string, object> item)
+ {
+ ((IDictionary<string, object>)_properties).Add(item);
+ _config.ReloadSources();
+ }
+
+ public void Clear()
+ {
+ _properties.Clear();
+ _config.ReloadSources();
+ }
+
+ public bool Contains(KeyValuePair<string, object> item)
+ {
+ return _properties.Contains(item);
+ }
+
+ public bool ContainsKey(string key)
+ {
+ return _properties.ContainsKey(key);
+ }
+
+ public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
+ {
+ ((IDictionary<string, object>)_properties).CopyTo(array, arrayIndex);
+ }
+
+ public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+ {
+ return _properties.GetEnumerator();
+ }
+
+ public bool Remove(string key)
+ {
+ var wasRemoved = _properties.Remove(key);
+ _config.ReloadSources();
+ return wasRemoved;
+ }
+
+ public bool Remove(KeyValuePair<string, object> item)
+ {
+ var wasRemoved = ((IDictionary<string, object>)_properties).Remove(item);
+ _config.ReloadSources();
+ return wasRemoved;
+ }
+
+ public bool TryGetValue(string key, out object value)
+ {
+ return _properties.TryGetValue(key, out value);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _properties.GetEnumerator();
+ }
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs
index 455efdb4176..cfe56a20978 100644
--- a/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs
+++ b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs
@@ -49,32 +49,8 @@ namespace Microsoft.Extensions.Configuration
/// <returns>The configuration value.</returns>
public string this[string key]
{
- get
- {
- for (int i = _providers.Count - 1; i >= 0; i--)
- {
- IConfigurationProvider provider = _providers[i];
-
- if (provider.TryGet(key, out string value))
- {
- return value;
- }
- }
-
- return null;
- }
- set
- {
- if (_providers.Count == 0)
- {
- throw new InvalidOperationException(SR.Error_NoSources);
- }
-
- foreach (IConfigurationProvider provider in _providers)
- {
- provider.Set(key, value);
- }
- }
+ get => GetConfiguration(_providers, key);
+ set => SetConfiguration(_providers, key, value);
}
/// <summary>
@@ -134,5 +110,33 @@ namespace Microsoft.Extensions.Configuration
(provider as IDisposable)?.Dispose();
}
}
+
+ internal static string GetConfiguration(IList<IConfigurationProvider> providers, string key)
+ {
+ for (int i = providers.Count - 1; i >= 0; i--)
+ {
+ IConfigurationProvider provider = providers[i];
+
+ if (provider.TryGet(key, out string value))
+ {
+ return value;
+ }
+ }
+
+ return null;
+ }
+
+ internal static void SetConfiguration(IList<IConfigurationProvider> providers, string key, string value)
+ {
+ if (providers.Count == 0)
+ {
+ throw new InvalidOperationException(SR.Error_NoSources);
+ }
+
+ foreach (IConfigurationProvider provider in providers)
+ {
+ provider.Set(key, value);
+ }
+ }
}
}
diff --git a/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationManagerTest.cs b/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationManagerTest.cs
new file mode 100644
index 00000000000..6e7a4919b25
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationManagerTest.cs
@@ -0,0 +1,1201 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.Configuration.Memory;
+using Microsoft.Extensions.Primitives;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Extensions.Configuration.Test
+{
+ public class ConfigurationManagerTest
+ {
+ [Fact]
+ public void AutoUpdates()
+ {
+ var config = new ConfigurationManager();
+
+ config.AddInMemoryCollection(new Dictionary<string, string>
+ {
+ { "TestKey", "TestValue" },
+ });
+
+ Assert.Equal("TestValue", config["TestKey"]);
+ }
+
+ [Fact]
+ public void TriggersReloadTokenOnSourceAddition()
+ {
+ var config = new ConfigurationManager();
+
+ var reloadToken = ((IConfiguration)config).GetReloadToken();
+
+ Assert.False(reloadToken.HasChanged);
+
+ config.AddInMemoryCollection(new Dictionary<string, string>
+ {
+ { "TestKey", "TestValue" },
+ });
+
+ Assert.True(reloadToken.HasChanged);
+ }
+
+ [Fact]
+ public void SettingValuesWorksWithoutManuallyAddingSource()
+ {
+ var config = new ConfigurationManager
+ {
+ ["TestKey"] = "TestValue",
+ };
+
+ Assert.Equal("TestValue", config["TestKey"]);
+ }
+
+ [Fact]
+ public void SettingConfigValuesDoesNotTriggerReloadToken()
+ {
+ var config = new ConfigurationManager();
+ var reloadToken = ((IConfiguration)config).GetReloadToken();
+
+ config["TestKey"] = "TestValue";
+
+ Assert.Equal("TestValue", config["TestKey"]);
+
+ // ConfigurationRoot doesn't fire the token today when the setter is called.
+ Assert.False(reloadToken.HasChanged);
+ }
+
+ [Fact]
+ public void SettingIConfigurationBuilderPropertiesReloadsSources()
+ {
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configBuilder = config;
+
+ config["PreReloadTestConfigKey"] = "PreReloadTestConfigValue";
+
+ var reloadToken1 = ((IConfiguration)config).GetReloadToken();
+ // Changing Properties causes all the IConfigurationSources to be reload.
+ configBuilder.Properties["TestPropertyKey"] = "TestPropertyValue";
+
+ var reloadToken2 = ((IConfiguration)config).GetReloadToken();
+ config["PostReloadTestConfigKey"] = "PostReloadTestConfigValue";
+
+ Assert.Equal("TestPropertyValue", configBuilder.Properties["TestPropertyKey"]);
+ Assert.Null(config["TestPropertyKey"]);
+
+ // Changes before the reload are lost by the MemoryConfigurationSource.
+ Assert.Null(config["PreReloadTestConfigKey"]);
+ Assert.Equal("PostReloadTestConfigValue", config["PostReloadTestConfigKey"]);
+
+ Assert.True(reloadToken1.HasChanged);
+ Assert.False(reloadToken2.HasChanged);
+ }
+
+ [Fact]
+ public void DisposesProvidersOnDispose()
+ {
+ var provider1 = new TestConfigurationProvider("foo", "foo-value");
+ var provider2 = new DisposableTestConfigurationProvider("bar", "bar-value");
+ var provider3 = new TestConfigurationProvider("baz", "baz-value");
+ var provider4 = new DisposableTestConfigurationProvider("qux", "qux-value");
+ var provider5 = new DisposableTestConfigurationProvider("quux", "quux-value");
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder builder = config;
+
+ builder.Add(new TestConfigurationSource(provider1));
+ builder.Add(new TestConfigurationSource(provider2));
+ builder.Add(new TestConfigurationSource(provider3));
+ builder.Add(new TestConfigurationSource(provider4));
+ builder.Add(new TestConfigurationSource(provider5));
+
+ Assert.Equal("foo-value", config["foo"]);
+ Assert.Equal("bar-value", config["bar"]);
+ Assert.Equal("baz-value", config["baz"]);
+ Assert.Equal("qux-value", config["qux"]);
+ Assert.Equal("quux-value", config["quux"]);
+
+ config.Dispose();
+
+ Assert.True(provider2.IsDisposed);
+ Assert.True(provider4.IsDisposed);
+ Assert.True(provider5.IsDisposed);
+ }
+
+ [Fact]
+ public void DisposesProvidersOnRemoval()
+ {
+ var provider1 = new TestConfigurationProvider("foo", "foo-value");
+ var provider2 = new DisposableTestConfigurationProvider("bar", "bar-value");
+ var provider3 = new TestConfigurationProvider("baz", "baz-value");
+ var provider4 = new DisposableTestConfigurationProvider("qux", "qux-value");
+ var provider5 = new DisposableTestConfigurationProvider("quux", "quux-value");
+
+ var source1 = new TestConfigurationSource(provider1);
+ var source2 = new TestConfigurationSource(provider2);
+ var source3 = new TestConfigurationSource(provider3);
+ var source4 = new TestConfigurationSource(provider4);
+ var source5 = new TestConfigurationSource(provider5);
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder builder = config;
+
+ builder.Add(source1);
+ builder.Add(source2);
+ builder.Add(source3);
+ builder.Add(source4);
+ builder.Add(source5);
+
+ Assert.Equal("foo-value", config["foo"]);
+ Assert.Equal("bar-value", config["bar"]);
+ Assert.Equal("baz-value", config["baz"]);
+ Assert.Equal("qux-value", config["qux"]);
+ Assert.Equal("quux-value", config["quux"]);
+
+ builder.Sources.Remove(source2);
+ builder.Sources.Remove(source4);
+
+ // While only provider2 and provider4 need to be disposed here, we do not assert provider5 is not disposed
+ // because even though it's unnecessary, Configuration disposes all providers on removal and rebuilds
+ // all the sources. While not optimal, this should be a pretty rare scenario.
+ Assert.True(provider2.IsDisposed);
+ Assert.True(provider4.IsDisposed);
+
+ config.Dispose();
+
+ Assert.True(provider2.IsDisposed);
+ Assert.True(provider4.IsDisposed);
+ Assert.True(provider5.IsDisposed);
+ }
+
+ [Fact]
+ public void DisposesChangeTokenRegistrationsOnDispose()
+ {
+ var changeToken = new TestChangeToken();
+ var providerMock = new Mock<IConfigurationProvider>();
+ providerMock.Setup(p => p.GetReloadToken()).Returns(changeToken);
+
+ var config = new ConfigurationManager();
+
+ ((IConfigurationBuilder)config).Add(new TestConfigurationSource(providerMock.Object));
+
+ Assert.NotEmpty(changeToken.Callbacks);
+
+ config.Dispose();
+
+ Assert.Empty(changeToken.Callbacks);
+ }
+
+ [Fact]
+ public void DisposesChangeTokenRegistrationsOnRemoval()
+ {
+ var changeToken = new TestChangeToken();
+ var providerMock = new Mock<IConfigurationProvider>();
+ providerMock.Setup(p => p.GetReloadToken()).Returns(changeToken);
+
+ var source = new TestConfigurationSource(providerMock.Object);
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder builder = config;
+
+ builder.Add(source);
+
+ Assert.NotEmpty(changeToken.Callbacks);
+
+ builder.Sources.Remove(source);
+
+ Assert.Empty(changeToken.Callbacks);
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void ChainedConfigurationIsDisposedOnDispose(bool shouldDispose)
+ {
+ var provider = new DisposableTestConfigurationProvider("foo", "foo-value");
+ var chainedConfig = new ConfigurationRoot(new IConfigurationProvider[] {
+ provider
+ });
+
+ var config = new ConfigurationManager();
+
+ config.AddConfiguration(chainedConfig, shouldDisposeConfiguration: shouldDispose);
+
+ Assert.False(provider.IsDisposed);
+
+ config.Dispose();
+
+ Assert.Equal(shouldDispose, provider.IsDisposed);
+ }
+
+ [Fact]
+ public void LoadAndCombineKeyValuePairsFromDifferentConfigurationProviders()
+ {
+ // Arrange
+ var dic1 = new Dictionary<string, string>()
+ {
+ {"Mem1:KeyInMem1", "ValueInMem1"}
+ };
+ var dic2 = new Dictionary<string, string>()
+ {
+ {"Mem2:KeyInMem2", "ValueInMem2"}
+ };
+ var dic3 = new Dictionary<string, string>()
+ {
+ {"Mem3:KeyInMem3", "ValueInMem3"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
+ var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 };
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ // Act
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+ configurationBuilder.Add(memConfigSrc3);
+
+ var memVal1 = config["mem1:keyinmem1"];
+ var memVal2 = config["Mem2:KeyInMem2"];
+ var memVal3 = config["MEM3:KEYINMEM3"];
+
+ // Assert
+ Assert.Contains(memConfigSrc1, configurationBuilder.Sources);
+ Assert.Contains(memConfigSrc2, configurationBuilder.Sources);
+ Assert.Contains(memConfigSrc3, configurationBuilder.Sources);
+
+ Assert.Equal("ValueInMem1", memVal1);
+ Assert.Equal("ValueInMem2", memVal2);
+ Assert.Equal("ValueInMem3", memVal3);
+
+ Assert.Equal("ValueInMem1", config["mem1:keyinmem1"]);
+ Assert.Equal("ValueInMem2", config["Mem2:KeyInMem2"]);
+ Assert.Equal("ValueInMem3", config["MEM3:KEYINMEM3"]);
+ Assert.Null(config["NotExist"]);
+ }
+
+ [Fact]
+ public void CanChainConfiguration()
+ {
+ // Arrange
+ var dic1 = new Dictionary<string, string>()
+ {
+ {"Mem1:KeyInMem1", "ValueInMem1"}
+ };
+ var dic2 = new Dictionary<string, string>()
+ {
+ {"Mem2:KeyInMem2", "ValueInMem2"}
+ };
+ var dic3 = new Dictionary<string, string>()
+ {
+ {"Mem3:KeyInMem3", "ValueInMem3"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
+ var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 };
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ // Act
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+ configurationBuilder.Add(memConfigSrc3);
+
+ var chained = new ConfigurationManager();
+ chained.AddConfiguration(config);
+ var memVal1 = chained["mem1:keyinmem1"];
+ var memVal2 = chained["Mem2:KeyInMem2"];
+ var memVal3 = chained["MEM3:KEYINMEM3"];
+
+ // Assert
+
+ Assert.Equal("ValueInMem1", memVal1);
+ Assert.Equal("ValueInMem2", memVal2);
+ Assert.Equal("ValueInMem3", memVal3);
+
+ Assert.Null(chained["NotExist"]);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void ChainedAsEnumerateFlattensIntoDictionaryTest(bool removePath)
+ {
+ // Arrange
+ var dic1 = new Dictionary<string, string>()
+ {
+ {"Mem1", "Value1"},
+ {"Mem1:", "NoKeyValue1"},
+ {"Mem1:KeyInMem1", "ValueInMem1"},
+ {"Mem1:KeyInMem1:Deep1", "ValueDeep1"}
+ };
+ var dic2 = new Dictionary<string, string>()
+ {
+ {"Mem2", "Value2"},
+ {"Mem2:", "NoKeyValue2"},
+ {"Mem2:KeyInMem2", "ValueInMem2"},
+ {"Mem2:KeyInMem2:Deep2", "ValueDeep2"}
+ };
+ var dic3 = new Dictionary<string, string>()
+ {
+ {"Mem3", "Value3"},
+ {"Mem3:", "NoKeyValue3"},
+ {"Mem3:KeyInMem3", "ValueInMem3"},
+ {"Mem3:KeyInMem3:Deep3", "ValueDeep3"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
+ var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 };
+
+ var config1 = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config1;
+
+ // Act
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+
+ var config2 = new ConfigurationManager();
+
+ config2
+ .AddConfiguration(config1)
+ .Add(memConfigSrc3);
+
+ var dict = config2.AsEnumerable(makePathsRelative: removePath).ToDictionary(k => k.Key, v => v.Value);
+
+ // Assert
+ Assert.Equal("Value1", dict["Mem1"]);
+ Assert.Equal("NoKeyValue1", dict["Mem1:"]);
+ Assert.Equal("ValueDeep1", dict["Mem1:KeyInMem1:Deep1"]);
+ Assert.Equal("ValueInMem2", dict["Mem2:KeyInMem2"]);
+ Assert.Equal("Value2", dict["Mem2"]);
+ Assert.Equal("NoKeyValue2", dict["Mem2:"]);
+ Assert.Equal("ValueDeep2", dict["Mem2:KeyInMem2:Deep2"]);
+ Assert.Equal("Value3", dict["Mem3"]);
+ Assert.Equal("NoKeyValue3", dict["Mem3:"]);
+ Assert.Equal("ValueInMem3", dict["Mem3:KeyInMem3"]);
+ Assert.Equal("ValueDeep3", dict["Mem3:KeyInMem3:Deep3"]);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void AsEnumerateFlattensIntoDictionaryTest(bool removePath)
+ {
+ // Arrange
+ var dic1 = new Dictionary<string, string>()
+ {
+ {"Mem1", "Value1"},
+ {"Mem1:", "NoKeyValue1"},
+ {"Mem1:KeyInMem1", "ValueInMem1"},
+ {"Mem1:KeyInMem1:Deep1", "ValueDeep1"}
+ };
+ var dic2 = new Dictionary<string, string>()
+ {
+ {"Mem2", "Value2"},
+ {"Mem2:", "NoKeyValue2"},
+ {"Mem2:KeyInMem2", "ValueInMem2"},
+ {"Mem2:KeyInMem2:Deep2", "ValueDeep2"}
+ };
+ var dic3 = new Dictionary<string, string>()
+ {
+ {"Mem3", "Value3"},
+ {"Mem3:", "NoKeyValue3"},
+ {"Mem3:KeyInMem3", "ValueInMem3"},
+ {"Mem3:KeyInMem3:Deep3", "ValueDeep3"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
+ var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 };
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ // Act
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+ configurationBuilder.Add(memConfigSrc3);
+ var dict = config.AsEnumerable(makePathsRelative: removePath).ToDictionary(k => k.Key, v => v.Value);
+
+ // Assert
+ Assert.Equal("Value1", dict["Mem1"]);
+ Assert.Equal("NoKeyValue1", dict["Mem1:"]);
+ Assert.Equal("ValueDeep1", dict["Mem1:KeyInMem1:Deep1"]);
+ Assert.Equal("ValueInMem2", dict["Mem2:KeyInMem2"]);
+ Assert.Equal("Value2", dict["Mem2"]);
+ Assert.Equal("NoKeyValue2", dict["Mem2:"]);
+ Assert.Equal("ValueDeep2", dict["Mem2:KeyInMem2:Deep2"]);
+ Assert.Equal("Value3", dict["Mem3"]);
+ Assert.Equal("NoKeyValue3", dict["Mem3:"]);
+ Assert.Equal("ValueInMem3", dict["Mem3:KeyInMem3"]);
+ Assert.Equal("ValueDeep3", dict["Mem3:KeyInMem3:Deep3"]);
+ }
+
+ [Fact]
+ public void AsEnumerateStripsKeyFromChildren()
+ {
+ // Arrange
+ var dic1 = new Dictionary<string, string>()
+ {
+ {"Mem1", "Value1"},
+ {"Mem1:", "NoKeyValue1"},
+ {"Mem1:KeyInMem1", "ValueInMem1"},
+ {"Mem1:KeyInMem1:Deep1", "ValueDeep1"}
+ };
+ var dic2 = new Dictionary<string, string>()
+ {
+ {"Mem2", "Value2"},
+ {"Mem2:", "NoKeyValue2"},
+ {"Mem2:KeyInMem2", "ValueInMem2"},
+ {"Mem2:KeyInMem2:Deep2", "ValueDeep2"}
+ };
+ var dic3 = new Dictionary<string, string>()
+ {
+ {"Mem3", "Value3"},
+ {"Mem3:", "NoKeyValue3"},
+ {"Mem3:KeyInMem3", "ValueInMem3"},
+ {"Mem3:KeyInMem4", "ValueInMem4"},
+ {"Mem3:KeyInMem3:Deep3", "ValueDeep3"},
+ {"Mem3:KeyInMem3:Deep4", "ValueDeep4"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
+ var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 };
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ // Act
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+ configurationBuilder.Add(memConfigSrc3);
+
+ var dict = config.GetSection("Mem1").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value);
+ Assert.Equal(3, dict.Count);
+ Assert.Equal("NoKeyValue1", dict[""]);
+ Assert.Equal("ValueInMem1", dict["KeyInMem1"]);
+ Assert.Equal("ValueDeep1", dict["KeyInMem1:Deep1"]);
+
+ var dict2 = config.GetSection("Mem2").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value);
+ Assert.Equal(3, dict2.Count);
+ Assert.Equal("NoKeyValue2", dict2[""]);
+ Assert.Equal("ValueInMem2", dict2["KeyInMem2"]);
+ Assert.Equal("ValueDeep2", dict2["KeyInMem2:Deep2"]);
+
+ var dict3 = config.GetSection("Mem3").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value);
+ Assert.Equal(5, dict3.Count);
+ Assert.Equal("NoKeyValue3", dict3[""]);
+ Assert.Equal("ValueInMem3", dict3["KeyInMem3"]);
+ Assert.Equal("ValueInMem4", dict3["KeyInMem4"]);
+ Assert.Equal("ValueDeep3", dict3["KeyInMem3:Deep3"]);
+ Assert.Equal("ValueDeep4", dict3["KeyInMem3:Deep4"]);
+ }
+
+ [Fact]
+ public void NewConfigurationProviderOverridesOldOneWhenKeyIsDuplicated()
+ {
+ // Arrange
+ var dic1 = new Dictionary<string, string>()
+ {
+ {"Key1:Key2", "ValueInMem1"}
+ };
+ var dic2 = new Dictionary<string, string>()
+ {
+ {"Key1:Key2", "ValueInMem2"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ // Act
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+
+ // Assert
+ Assert.Equal("ValueInMem2", config["Key1:Key2"]);
+ }
+
+ [Fact]
+ public void NewConfigurationRootMayBeBuiltFromExistingWithDuplicateKeys()
+ {
+ var configurationRoot = new ConfigurationManager();
+
+ configurationRoot.AddInMemoryCollection(new Dictionary<string, string>
+ {
+ {"keya:keyb", "valueA"},
+ });
+ configurationRoot.AddInMemoryCollection(new Dictionary<string, string>
+ {
+ {"KEYA:KEYB", "valueB"},
+ });
+
+ var newConfigurationRoot = new ConfigurationManager();
+
+ newConfigurationRoot.AddInMemoryCollection(configurationRoot.AsEnumerable());
+
+ Assert.Equal("valueB", newConfigurationRoot["keya:keyb"]);
+ }
+
+ [Fact]
+ public void SettingValueUpdatesAllConfigurationProviders()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>()
+ {
+ {"Key1", "Value1"},
+ {"Key2", "Value2"}
+ };
+
+ var memConfigSrc1 = new TestMemorySourceProvider(dict);
+ var memConfigSrc2 = new TestMemorySourceProvider(dict);
+ var memConfigSrc3 = new TestMemorySourceProvider(dict);
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+ configurationBuilder.Add(memConfigSrc3);
+
+ // Act
+ config["Key1"] = "NewValue1";
+ config["Key2"] = "NewValue2";
+
+ var memConfigProvider1 = memConfigSrc1.Build(configurationBuilder);
+ var memConfigProvider2 = memConfigSrc2.Build(configurationBuilder);
+ var memConfigProvider3 = memConfigSrc3.Build(configurationBuilder);
+
+ // Assert
+ Assert.Equal("NewValue1", config["Key1"]);
+ Assert.Equal("NewValue1", Get(memConfigProvider1, "Key1"));
+ Assert.Equal("NewValue1", Get(memConfigProvider2, "Key1"));
+ Assert.Equal("NewValue1", Get(memConfigProvider3, "Key1"));
+ Assert.Equal("NewValue2", config["Key2"]);
+ Assert.Equal("NewValue2", Get(memConfigProvider1, "Key2"));
+ Assert.Equal("NewValue2", Get(memConfigProvider2, "Key2"));
+ Assert.Equal("NewValue2", Get(memConfigProvider3, "Key2"));
+ }
+
+ [Fact]
+ public void CanGetConfigurationSection()
+ {
+ // Arrange
+ var dic1 = new Dictionary<string, string>()
+ {
+ {"Data:DB1:Connection1", "MemVal1"},
+ {"Data:DB1:Connection2", "MemVal2"}
+ };
+ var dic2 = new Dictionary<string, string>()
+ {
+ {"DataSource:DB2:Connection", "MemVal3"}
+ };
+ var dic3 = new Dictionary<string, string>()
+ {
+ {"Data", "MemVal4"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
+ var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 };
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+ configurationBuilder.Add(memConfigSrc3);
+
+ // Act
+ var configFocus = config.GetSection("Data");
+
+ var memVal1 = configFocus["DB1:Connection1"];
+ var memVal2 = configFocus["DB1:Connection2"];
+ var memVal3 = configFocus["DB2:Connection"];
+ var memVal4 = configFocus["Source:DB2:Connection"];
+ var memVal5 = configFocus.Value;
+
+ // Assert
+ Assert.Equal("MemVal1", memVal1);
+ Assert.Equal("MemVal2", memVal2);
+ Assert.Equal("MemVal4", memVal5);
+
+ Assert.Equal("MemVal1", configFocus["DB1:Connection1"]);
+ Assert.Equal("MemVal2", configFocus["DB1:Connection2"]);
+ Assert.Null(configFocus["DB2:Connection"]);
+ Assert.Null(configFocus["Source:DB2:Connection"]);
+ Assert.Equal("MemVal4", configFocus.Value);
+ }
+
+ [Fact]
+ public void CanGetConnectionStrings()
+ {
+ // Arrange
+ var dic1 = new Dictionary<string, string>()
+ {
+ {"ConnectionStrings:DB1:Connection1", "MemVal1"},
+ {"ConnectionStrings:DB1:Connection2", "MemVal2"}
+ };
+ var dic2 = new Dictionary<string, string>()
+ {
+ {"ConnectionStrings:DB2:Connection", "MemVal3"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+
+ // Act
+ var memVal1 = config.GetConnectionString("DB1:Connection1");
+ var memVal2 = config.GetConnectionString("DB1:Connection2");
+ var memVal3 = config.GetConnectionString("DB2:Connection");
+
+ // Assert
+ Assert.Equal("MemVal1", memVal1);
+ Assert.Equal("MemVal2", memVal2);
+ Assert.Equal("MemVal3", memVal3);
+ }
+
+ [Fact]
+ public void CanGetConfigurationChildren()
+ {
+ // Arrange
+ var dic1 = new Dictionary<string, string>()
+ {
+ {"Data:DB1:Connection1", "MemVal1"},
+ {"Data:DB1:Connection2", "MemVal2"}
+ };
+ var dic2 = new Dictionary<string, string>()
+ {
+ {"Data:DB2Connection", "MemVal3"}
+ };
+ var dic3 = new Dictionary<string, string>()
+ {
+ {"DataSource:DB3:Connection", "MemVal4"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
+ var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 };
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+ configurationBuilder.Add(memConfigSrc3);
+
+ // Act
+ var configSections = config.GetSection("Data").GetChildren().ToList();
+
+ // Assert
+ Assert.Equal(2, configSections.Count());
+ Assert.Equal("MemVal1", configSections.FirstOrDefault(c => c.Key == "DB1")["Connection1"]);
+ Assert.Equal("MemVal2", configSections.FirstOrDefault(c => c.Key == "DB1")["Connection2"]);
+ Assert.Equal("MemVal3", configSections.FirstOrDefault(c => c.Key == "DB2Connection").Value);
+ Assert.False(configSections.Exists(c => c.Key == "DB3"));
+ Assert.False(configSections.Exists(c => c.Key == "DB3"));
+ }
+
+ [Fact]
+ public void SourcesReturnsAddedConfigurationProviders()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>()
+ {
+ {"Mem:KeyInMem", "MemVal"}
+ };
+ var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dict };
+ var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dict };
+ var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dict };
+
+ var config = new ConfigurationManager();
+ IConfigurationBuilder configurationBuilder = config;
+
+ // Act
+
+ // A MemoryConfigurationSource is added by default, so there will be no error unless we clear it
+ configurationBuilder.Sources.Clear();
+ configurationBuilder.Add(memConfigSrc1);
+ configurationBuilder.Add(memConfigSrc2);
+ configurationBuilder.Add(memConfigSrc3);
+
+ // Assert
+ Assert.Equal(new[] { memConfigSrc1, memConfigSrc2, memConfigSrc3 }, configurationBuilder.Sources);
+ }
+
+ [Fact]
+ public void SetValueThrowsExceptionNoSourceRegistered()
+ {
+ // Arrange
+ var config = new ConfigurationManager();
+
+ // A MemoryConfigurationSource is added by default, so there will be no error unless we clear it
+ config["Title"] = "Welcome";
+
+ ((IConfigurationBuilder)config).Sources.Clear();
+
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => config["Title"] = "Welcome");
+
+ // Assert
+ Assert.Equal(SR.Error_NoSources, ex.Message);
+ }
+
+ [Fact]
+ public void SameReloadTokenIsReturnedRepeatedly()
+ {
+ // Arrange
+ IConfiguration config = new ConfigurationManager();
+
+ // Act
+ var token1 = config.GetReloadToken();
+ var token2 = config.GetReloadToken();
+
+ // Assert
+ Assert.Same(token1, token2);
+ }
+
+ [Fact]
+ public void DifferentReloadTokenReturnedAfterReloading()
+ {
+ // Arrange
+ IConfigurationRoot config = new ConfigurationManager();
+
+ // Act
+ var token1 = config.GetReloadToken();
+ var token2 = config.GetReloadToken();
+ config.Reload();
+ var token3 = config.GetReloadToken();
+ var token4 = config.GetReloadToken();
+
+ // Assert
+ Assert.Same(token1, token2);
+ Assert.Same(token3, token4);
+ Assert.NotSame(token1, token3);
+ }
+
+ [Fact]
+ public void TokenTriggeredWhenReloadOccurs()
+ {
+ // Arrange
+ IConfigurationRoot config = new ConfigurationManager();
+
+ // Act
+ var token1 = config.GetReloadToken();
+ var hasChanged1 = token1.HasChanged;
+ config.Reload();
+ var hasChanged2 = token1.HasChanged;
+
+ // Assert
+ Assert.False(hasChanged1);
+ Assert.True(hasChanged2);
+ }
+
+ [Fact]
+ public void MultipleCallbacksCanBeRegisteredToReload()
+ {
+ // Arrange
+ IConfigurationRoot config = new ConfigurationManager();
+
+ // Act
+ var token1 = config.GetReloadToken();
+ var called1 = 0;
+ token1.RegisterChangeCallback(_ => called1++, state: null);
+ var called2 = 0;
+ token1.RegisterChangeCallback(_ => called2++, state: null);
+
+ // Assert
+ Assert.Equal(0, called1);
+ Assert.Equal(0, called2);
+
+ config.Reload();
+ Assert.Equal(1, called1);
+ Assert.Equal(1, called2);
+
+ var token2 = config.GetReloadToken();
+ var cleanup1 = token2.RegisterChangeCallback(_ => called1++, state: null);
+ token2.RegisterChangeCallback(_ => called2++, state: null);
+
+ cleanup1.Dispose();
+
+ config.Reload();
+ Assert.Equal(1, called1);
+ Assert.Equal(2, called2);
+ }
+
+ [Fact]
+ public void NewTokenAfterReloadIsNotChanged()
+ {
+ // Arrange
+ IConfigurationRoot config = new ConfigurationManager();
+
+ // Act
+ var token1 = config.GetReloadToken();
+ var hasChanged1 = token1.HasChanged;
+ config.Reload();
+ var hasChanged2 = token1.HasChanged;
+ var token2 = config.GetReloadToken();
+ var hasChanged3 = token2.HasChanged;
+
+ //
+ // Assert
+ Assert.False(hasChanged1);
+ Assert.True(hasChanged2);
+ Assert.False(hasChanged3);
+ Assert.NotSame(token1, token2);
+ }
+
+ [Fact]
+ public void KeyStartingWithColonMeansFirstSectionHasEmptyName()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>
+ {
+ [":Key2"] = "value"
+ };
+ var config = new ConfigurationManager();
+ config.AddInMemoryCollection(dict);
+
+ // Act
+ var children = config.GetChildren().ToArray();
+
+ // Assert
+ Assert.Single(children);
+ Assert.Equal(string.Empty, children.First().Key);
+ Assert.Single(children.First().GetChildren());
+ Assert.Equal("Key2", children.First().GetChildren().First().Key);
+ }
+
+ [Fact]
+ public void KeyWithDoubleColonHasSectionWithEmptyName()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>
+ {
+ ["Key1::Key3"] = "value"
+ };
+
+ var config = new ConfigurationManager();
+ ((IConfigurationBuilder)config).AddInMemoryCollection(dict);
+
+ // Act
+ var children = config.GetChildren().ToArray();
+
+ // Assert
+ Assert.Single(children);
+ Assert.Equal("Key1", children.First().Key);
+ Assert.Single(children.First().GetChildren());
+ Assert.Equal(string.Empty, children.First().GetChildren().First().Key);
+ Assert.Single(children.First().GetChildren().First().GetChildren());
+ Assert.Equal("Key3", children.First().GetChildren().First().GetChildren().First().Key);
+ }
+
+ [Fact]
+ public void KeyEndingWithColonMeansLastSectionHasEmptyName()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>
+ {
+ ["Key1:"] = "value"
+ };
+
+ var config = new ConfigurationManager();
+ ((IConfigurationBuilder)config).AddInMemoryCollection(dict);
+
+ // Act
+ var children = config.GetChildren().ToArray();
+
+ // Assert
+ Assert.Single(children);
+ Assert.Equal("Key1", children.First().Key);
+ Assert.Single(children.First().GetChildren());
+ Assert.Equal(string.Empty, children.First().GetChildren().First().Key);
+ }
+
+ [Fact]
+ public void SectionWithValueExists()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>()
+ {
+ {"Mem1", "Value1"},
+ {"Mem1:KeyInMem1", "ValueInMem1"},
+ {"Mem1:KeyInMem1:Deep1", "ValueDeep1"}
+ };
+
+ var config = new ConfigurationManager();
+ ((IConfigurationBuilder)config).AddInMemoryCollection(dict);
+
+ // Act
+ var sectionExists1 = config.GetSection("Mem1").Exists();
+ var sectionExists2 = config.GetSection("Mem1:KeyInMem1").Exists();
+ var sectionNotExists = config.GetSection("Mem2").Exists();
+
+ // Assert
+ Assert.True(sectionExists1);
+ Assert.True(sectionExists2);
+ Assert.False(sectionNotExists);
+ }
+
+ [Fact]
+ public void SectionGetRequiredSectionSuccess()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>()
+ {
+ {"Mem1", "Value1"},
+ {"Mem1:KeyInMem1", "ValueInMem1"},
+ {"Mem1:KeyInMem1:Deep1", "ValueDeep1"}
+ };
+
+ var config = new ConfigurationManager();
+ ((IConfigurationBuilder)config).AddInMemoryCollection(dict);
+
+ // Act
+ var sectionExists1 = config.GetRequiredSection("Mem1").Exists();
+ var sectionExists2 = config.GetRequiredSection("Mem1:KeyInMem1").Exists();
+
+ // Assert
+ Assert.True(sectionExists1);
+ Assert.True(sectionExists2);
+ }
+
+ [Fact]
+ public void SectionGetRequiredSectionMissingThrowException()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>()
+ {
+ {"Mem1", "Value1"},
+ {"Mem1:Deep1", "Value1"},
+ };
+
+ var config = new ConfigurationManager();
+ ((IConfigurationBuilder)config).AddInMemoryCollection(dict);
+
+ Assert.Throws<InvalidOperationException>(() => config.GetRequiredSection("Mem2"));
+ Assert.Throws<InvalidOperationException>(() => config.GetRequiredSection("Mem1:Deep2"));
+ }
+
+ [Fact]
+ public void SectionWithChildrenExists()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>()
+ {
+ {"Mem1:KeyInMem1", "ValueInMem1"},
+ {"Mem1:KeyInMem1:Deep1", "ValueDeep1"},
+ {"Mem2:KeyInMem2:Deep1", "ValueDeep2"}
+ };
+
+ var config = new ConfigurationManager();
+ ((IConfigurationBuilder)config).AddInMemoryCollection(dict);
+
+ // Act
+ var sectionExists1 = config.GetSection("Mem1").Exists();
+ var sectionExists2 = config.GetSection("Mem2").Exists();
+ var sectionNotExists = config.GetSection("Mem3").Exists();
+
+ // Assert
+ Assert.True(sectionExists1);
+ Assert.True(sectionExists2);
+ Assert.False(sectionNotExists);
+ }
+
+ [Theory]
+ [InlineData("Value1")]
+ [InlineData("")]
+ public void KeyWithValueAndWithoutChildrenExistsAsSection(string value)
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>()
+ {
+ {"Mem1", value}
+ };
+
+ var config = new ConfigurationManager();
+ ((IConfigurationBuilder)config).AddInMemoryCollection(dict);
+
+ // Act
+ var sectionExists = config.GetSection("Mem1").Exists();
+
+ // Assert
+ Assert.True(sectionExists);
+ }
+
+ [Fact]
+ public void KeyWithNullValueAndWithoutChildrenIsASectionButNotExists()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>()
+ {
+ {"Mem1", null}
+ };
+
+ var config = new ConfigurationManager();
+ ((IConfigurationBuilder)config).AddInMemoryCollection(dict);
+
+ // Act
+ var sections = config.GetChildren();
+ var sectionExists = config.GetSection("Mem1").Exists();
+ var sectionChildren = config.GetSection("Mem1").GetChildren();
+
+ // Assert
+ Assert.Single(sections, section => section.Key == "Mem1");
+ Assert.False(sectionExists);
+ Assert.Empty(sectionChildren);
+ }
+
+ [Fact]
+ public void SectionWithChildrenHasNullValue()
+ {
+ // Arrange
+ var dict = new Dictionary<string, string>()
+ {
+ {"Mem1:KeyInMem1", "ValueInMem1"},
+ };
+
+
+ var config = new ConfigurationManager();
+ ((IConfigurationBuilder)config).AddInMemoryCollection(dict);
+
+ // Act
+ var sectionValue = config.GetSection("Mem1").Value;
+
+ // Assert
+ Assert.Null(sectionValue);
+ }
+
+ [Fact]
+ public void ProviderWithNullReloadToken()
+ {
+ // Arrange
+ var config = new ConfigurationManager();
+ IConfigurationBuilder builder = config;
+
+ // Assert
+ Assert.NotNull(builder.Build());
+ }
+
+ [Fact]
+ public void BuildReturnsThis()
+ {
+ // Arrange
+ var config = new ConfigurationManager();
+
+ // Assert
+ Assert.Same(config, ((IConfigurationBuilder)config).Build());
+ }
+
+ private static string Get(IConfigurationProvider provider, string key)
+ {
+ string value;
+
+ if (!provider.TryGet(key, out value))
+ {
+ throw new InvalidOperationException("Key not found");
+ }
+
+ return value;
+ }
+
+ private class TestConfigurationSource : IConfigurationSource
+ {
+ private readonly IConfigurationProvider _provider;
+
+ public TestConfigurationSource(IConfigurationProvider provider)
+ {
+ _provider = provider;
+ }
+
+ public IConfigurationProvider Build(IConfigurationBuilder builder)
+ {
+ return _provider;
+ }
+ }
+
+ private class TestConfigurationProvider : ConfigurationProvider
+ {
+ public TestConfigurationProvider(string key, string value)
+ => Data.Add(key, value);
+ }
+
+ private class DisposableTestConfigurationProvider : ConfigurationProvider, IDisposable
+ {
+ public bool IsDisposed { get; set; }
+
+ public DisposableTestConfigurationProvider(string key, string value)
+ => Data.Add(key, value);
+
+ public void Dispose()
+ => IsDisposed = true;
+ }
+
+ private class TestChangeToken : IChangeToken
+ {
+ public List<(Action<object>, object)> Callbacks { get; } = new List<(Action<object>, object)>();
+
+ public bool HasChanged => false;
+
+ public bool ActiveChangeCallbacks => true;
+
+ public IDisposable RegisterChangeCallback(Action<object> callback, object state)
+ {
+ var item = (callback, state);
+ Callbacks.Add(item);
+ return new DisposableAction(() => Callbacks.Remove(item));
+ }
+
+ private class DisposableAction : IDisposable
+ {
+ private Action _action;
+
+ public DisposableAction(Action action)
+ {
+ _action = action;
+ }
+
+ public void Dispose()
+ {
+ var a = _action;
+ if (a != null)
+ {
+ _action = null;
+ a();
+ }
+ }
+ }
+ }
+
+ private class TestMemorySourceProvider : MemoryConfigurationProvider, IConfigurationSource
+ {
+ public TestMemorySourceProvider(Dictionary<string, string> initialData)
+ : base(new MemoryConfigurationSource { InitialData = initialData })
+ { }
+
+ public IConfigurationProvider Build(IConfigurationBuilder builder)
+ {
+ return this;
+ }
+ }
+
+ private class NullReloadTokenConfigSource : IConfigurationSource, IConfigurationProvider
+ {
+ public IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath) => throw new NotImplementedException();
+ public IChangeToken GetReloadToken() => null;
+ public void Load() { }
+ public void Set(string key, string value) => throw new NotImplementedException();
+ public bool TryGet(string key, out string value) => throw new NotImplementedException();
+ public IConfigurationProvider Build(IConfigurationBuilder builder) => this;
+ }
+
+ }
+}