diff --git a/src/Configuration/Config.Abstractions/src/ConfigurationRootExtensions.cs b/src/Configuration/Config.Abstractions/src/ConfigurationRootExtensions.cs
new file mode 100644
index 00000000000..b6a9024c03a
--- /dev/null
+++ b/src/Configuration/Config.Abstractions/src/ConfigurationRootExtensions.cs
@@ -0,0 +1,75 @@
+// 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.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Microsoft.Extensions.Configuration
+{
+ ///
+ /// Extension methods for .
+ ///
+ public static class ConfigurationRootExtensions
+ {
+ ///
+ /// Generates a human-readable view of the configuration showing where each value came from.
+ ///
+ /// The debug view.
+ public static string GetDebugView(this IConfigurationRoot root)
+ {
+ void RecurseChildren(
+ StringBuilder stringBuilder,
+ IEnumerable children,
+ string indent)
+ {
+ foreach (var child in children)
+ {
+ var valueAndProvider = GetValueAndProvider(root, child.Path);
+
+ if (valueAndProvider.Provider != null)
+ {
+ stringBuilder
+ .Append(indent)
+ .Append(child.Key)
+ .Append("=")
+ .Append(valueAndProvider.Value)
+ .Append(" (")
+ .Append(valueAndProvider.Provider)
+ .AppendLine(")");
+ }
+ else
+ {
+ stringBuilder
+ .Append(indent)
+ .Append(child.Key)
+ .AppendLine(":");
+ }
+
+ RecurseChildren(stringBuilder, child.GetChildren(), indent + " ");
+ }
+ }
+
+ var builder = new StringBuilder();
+
+ RecurseChildren(builder, root.GetChildren(), "");
+
+ return builder.ToString();
+ }
+
+ private static (string Value, IConfigurationProvider Provider) GetValueAndProvider(
+ IConfigurationRoot root,
+ string key)
+ {
+ foreach (var provider in root.Providers.Reverse())
+ {
+ if (provider.TryGet(key, out var value))
+ {
+ return (value, provider);
+ }
+ }
+
+ return (null, null);
+ }
+ }
+}
diff --git a/src/Configuration/Config.Abstractions/src/IConfigurationProvider.cs b/src/Configuration/Config.Abstractions/src/IConfigurationProvider.cs
index fa951582835..949826fd565 100644
--- a/src/Configuration/Config.Abstractions/src/IConfigurationProvider.cs
+++ b/src/Configuration/Config.Abstractions/src/IConfigurationProvider.cs
@@ -47,4 +47,4 @@ public interface IConfigurationProvider
/// The child keys.
IEnumerable GetChildKeys(IEnumerable earlierKeys, string parentPath);
}
-}
\ No newline at end of file
+}
diff --git a/src/Configuration/Config.Abstractions/src/IConfigurationRoot.cs b/src/Configuration/Config.Abstractions/src/IConfigurationRoot.cs
index 4587294a293..2d0750da058 100644
--- a/src/Configuration/Config.Abstractions/src/IConfigurationRoot.cs
+++ b/src/Configuration/Config.Abstractions/src/IConfigurationRoot.cs
@@ -1,7 +1,6 @@
// 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;
namespace Microsoft.Extensions.Configuration
diff --git a/src/Configuration/Config.AzureKeyVault/test/ConfigurationProviderAzureKeyVaultTest.cs b/src/Configuration/Config.AzureKeyVault/test/ConfigurationProviderAzureKeyVaultTest.cs
index fad8cb90b17..9eb15353cb1 100644
--- a/src/Configuration/Config.AzureKeyVault/test/ConfigurationProviderAzureKeyVaultTest.cs
+++ b/src/Configuration/Config.AzureKeyVault/test/ConfigurationProviderAzureKeyVaultTest.cs
@@ -14,6 +14,11 @@ namespace Microsoft.Extensions.Configuration.AzureKeyVault.Test
{
public class ConfigurationProviderKeyVaultTest : ConfigurationProviderTestBase
{
+ public override void Null_values_are_included_in_the_config()
+ {
+ AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true);
+ }
+
protected override (IConfigurationProvider Provider, System.Action Initializer) LoadThroughProvider(
TestSection testConfig)
{
diff --git a/src/Configuration/Config.EnvironmentVariables/test/ConfigurationProviderEnvironmentVariablesTest.cs b/src/Configuration/Config.EnvironmentVariables/test/ConfigurationProviderEnvironmentVariablesTest.cs
index 34853944ed6..65736ff2ae6 100644
--- a/src/Configuration/Config.EnvironmentVariables/test/ConfigurationProviderEnvironmentVariablesTest.cs
+++ b/src/Configuration/Config.EnvironmentVariables/test/ConfigurationProviderEnvironmentVariablesTest.cs
@@ -26,5 +26,10 @@ public override void Load_from_single_provider_with_differing_case_duplicates_th
{
AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.DuplicatesDifferentCaseTestConfig)));
}
+
+ public override void Null_values_are_included_in_the_config()
+ {
+ AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true);
+ }
}
}
diff --git a/src/Configuration/Config.FileExtensions/src/FileConfigurationProvider.cs b/src/Configuration/Config.FileExtensions/src/FileConfigurationProvider.cs
index 92c29faafcb..47fff096d61 100644
--- a/src/Configuration/Config.FileExtensions/src/FileConfigurationProvider.cs
+++ b/src/Configuration/Config.FileExtensions/src/FileConfigurationProvider.cs
@@ -42,6 +42,13 @@ public FileConfigurationProvider(FileConfigurationSource source)
/// The source settings for this provider.
///
public FileConfigurationSource Source { get; }
+
+ ///
+ /// Generates a string representing this provider name and relevant details.
+ ///
+ /// The configuration name.
+ public override string ToString()
+ => $"{GetType().Name} for '{Source.Path}' ({(Source.Optional ? "Optional" : "Required")})";
private void Load(bool reload)
{
diff --git a/src/Configuration/Config.Json/test/ConfigurationProviderJsonTest.cs b/src/Configuration/Config.Json/test/ConfigurationProviderJsonTest.cs
index 9413785580b..b58263a082b 100644
--- a/src/Configuration/Config.Json/test/ConfigurationProviderJsonTest.cs
+++ b/src/Configuration/Config.Json/test/ConfigurationProviderJsonTest.cs
@@ -35,13 +35,15 @@ protected override (IConfigurationProvider Provider, Action Initializer) LoadThr
private void SectionToJson(StringBuilder jsonBuilder, TestSection section)
{
+ string ValueToJson(object value) => value == null ? "null" : $"'{value}'";
+
jsonBuilder.AppendLine("{");
foreach (var tuple in section.Values)
{
jsonBuilder.AppendLine(tuple.Value.AsArray != null
- ? $"'{tuple.Key}': [{string.Join(", ", tuple.Value.AsArray.Select(v => $"'{v}'"))}],"
- : $"'{tuple.Key}': '{tuple.Value.AsString}',");
+ ? $"'{tuple.Key}': [{string.Join(", ", tuple.Value.AsArray.Select(ValueToJson))}],"
+ : $"'{tuple.Key}': {ValueToJson(tuple.Value.AsString)},");
}
foreach (var tuple in section.Sections)
diff --git a/src/Configuration/Config.KeyPerFile/src/KeyPerFileConfigurationProvider.cs b/src/Configuration/Config.KeyPerFile/src/KeyPerFileConfigurationProvider.cs
index 47488957441..13541110e63 100644
--- a/src/Configuration/Config.KeyPerFile/src/KeyPerFileConfigurationProvider.cs
+++ b/src/Configuration/Config.KeyPerFile/src/KeyPerFileConfigurationProvider.cs
@@ -39,10 +39,8 @@ public override void Load()
{
return;
}
- else
- {
- throw new DirectoryNotFoundException("A non-null file provider for the directory is required when this source is not optional.");
- }
+
+ throw new DirectoryNotFoundException("A non-null file provider for the directory is required when this source is not optional.");
}
var directory = Source.FileProvider.GetDirectoryContents("/");
@@ -68,5 +66,15 @@ public override void Load()
}
}
}
+
+ private string GetDirectoryName()
+ => Source.FileProvider?.GetFileInfo("/")?.PhysicalPath ?? "";
+
+ ///
+ /// Generates a string representing this provider name and relevant details.
+ ///
+ /// The configuration name.
+ public override string ToString()
+ => $"{GetType().Name} for files in '{GetDirectoryName()}' ({(Source.Optional ? "Optional" : "Required")})";
}
}
diff --git a/src/Configuration/Config.KeyPerFile/test/KeyPerFileTests.cs b/src/Configuration/Config.KeyPerFile/test/KeyPerFileTests.cs
index fac0e839e8b..4de528c0116 100644
--- a/src/Configuration/Config.KeyPerFile/test/KeyPerFileTests.cs
+++ b/src/Configuration/Config.KeyPerFile/test/KeyPerFileTests.cs
@@ -190,20 +190,11 @@ public TestFileProvider(params IFileInfo[] files)
_contents = new TestDirectoryContents(files);
}
- public IDirectoryContents GetDirectoryContents(string subpath)
- {
- return _contents;
- }
+ public IDirectoryContents GetDirectoryContents(string subpath) => _contents;
- public IFileInfo GetFileInfo(string subpath)
- {
- throw new NotImplementedException();
- }
+ public IFileInfo GetFileInfo(string subpath) => new TestFile("TestDirectory");
- public IChangeToken Watch(string filter)
- {
- throw new NotImplementedException();
- }
+ public IChangeToken Watch(string filter) => throw new NotImplementedException();
}
class TestDirectoryContents : IDirectoryContents
@@ -215,75 +206,33 @@ public TestDirectoryContents(params IFileInfo[] files)
_list = new List(files);
}
- public bool Exists
- {
- get
- {
- return true;
- }
- }
+ public bool Exists => true;
- public IEnumerator GetEnumerator()
- {
- return _list.GetEnumerator();
- }
+ public IEnumerator GetEnumerator() => _list.GetEnumerator();
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
//TODO: Probably need a directory and file type.
class TestFile : IFileInfo
{
- private string _name;
- private string _contents;
+ private readonly string _name;
+ private readonly string _contents;
- public bool Exists
- {
- get
- {
- return true;
- }
- }
+ public bool Exists => true;
public bool IsDirectory
{
get;
}
- public DateTimeOffset LastModified
- {
- get
- {
- throw new NotImplementedException();
- }
- }
+ public DateTimeOffset LastModified => throw new NotImplementedException();
- public long Length
- {
- get
- {
- throw new NotImplementedException();
- }
- }
+ public long Length => throw new NotImplementedException();
- public string Name
- {
- get
- {
- return _name;
- }
- }
+ public string Name => _name;
- public string PhysicalPath
- {
- get
- {
- throw new NotImplementedException();
- }
- }
+ public string PhysicalPath => "Root/" + Name;
public TestFile(string name)
{
@@ -304,7 +253,9 @@ public Stream CreateReadStream()
throw new InvalidOperationException("Cannot create stream from directory");
}
- return new MemoryStream(Encoding.UTF8.GetBytes(_contents));
+ return _contents == null
+ ? new MemoryStream()
+ : new MemoryStream(Encoding.UTF8.GetBytes(_contents));
}
}
}
diff --git a/src/Configuration/Config.Xml/test/ConfigurationProviderXmlTest.cs b/src/Configuration/Config.Xml/test/ConfigurationProviderXmlTest.cs
index 9d42d8c207a..6e235149867 100644
--- a/src/Configuration/Config.Xml/test/ConfigurationProviderXmlTest.cs
+++ b/src/Configuration/Config.Xml/test/ConfigurationProviderXmlTest.cs
@@ -10,11 +10,42 @@ namespace Microsoft.Extensions.Configuration.Xml.Test
{
public class ConfigurationProviderXmlTest : ConfigurationProviderTestBase
{
+ public override void Combine_before_other_provider()
+ {
+ // Disabled test due to XML handling of empty section.
+ }
+
public override void Combine_after_other_provider()
{
// Disabled test due to XML handling of empty section.
}
+ public override void Has_debug_view()
+ {
+ var configRoot = BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig));
+ var providerTag = configRoot.Providers.Single().ToString();
+
+ var expected =
+ $@"Key1=Value1 ({providerTag})
+Section1:
+ Key2=Value12 ({providerTag})
+ Section2:
+ Key3=Value123 ({providerTag})
+ Key3a:
+ 0=ArrayValue0 ({providerTag})
+ Name=0 ({providerTag})
+ 1=ArrayValue1 ({providerTag})
+ Name=1 ({providerTag})
+ 2=ArrayValue2 ({providerTag})
+ Name=2 ({providerTag})
+Section3:
+ Section4:
+ Key4=Value344 ({providerTag})
+";
+
+ AssertDebugView(configRoot, expected);
+ }
+
protected override (IConfigurationProvider Provider, Action Initializer) LoadThroughProvider(TestSection testConfig)
{
var xmlBuilder = new StringBuilder();
@@ -36,7 +67,7 @@ private void SectionToXml(StringBuilder xmlBuilder, string sectionName, TestSect
foreach (var tuple in section.Values)
{
- if (tuple.Value.AsString != null)
+ if (tuple.Value.AsArray == null)
{
xmlBuilder.AppendLine($"<{tuple.Key}>{tuple.Value.AsString}{tuple.Key}>");
}
diff --git a/src/Configuration/Config/src/ConfigurationProvider.cs b/src/Configuration/Config/src/ConfigurationProvider.cs
index 8011a6b218e..4e0d438f707 100644
--- a/src/Configuration/Config/src/ConfigurationProvider.cs
+++ b/src/Configuration/Config/src/ConfigurationProvider.cs
@@ -94,5 +94,11 @@ protected void OnReload()
var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
previousToken.OnReload();
}
+
+ ///
+ /// Generates a string representing this provider name and relevant details.
+ ///
+ /// The configuration name.
+ public override string ToString() => $"{GetType().Name}";
}
}
diff --git a/src/Configuration/Config/src/ConfigurationRoot.cs b/src/Configuration/Config/src/ConfigurationRoot.cs
index 275efa88126..f104e8178ce 100644
--- a/src/Configuration/Config/src/ConfigurationRoot.cs
+++ b/src/Configuration/Config/src/ConfigurationRoot.cs
@@ -14,7 +14,7 @@ namespace Microsoft.Extensions.Configuration
///
public class ConfigurationRoot : IConfigurationRoot
{
- private IList _providers;
+ private readonly IList _providers;
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
///
@@ -52,9 +52,7 @@ public string this[string key]
{
foreach (var provider in _providers.Reverse())
{
- string value;
-
- if (provider.TryGet(key, out value))
+ if (provider.TryGet(key, out var value))
{
return value;
}
@@ -62,7 +60,6 @@ public string this[string key]
return null;
}
-
set
{
if (!_providers.Any())
diff --git a/src/Configuration/Config/src/ConfigurationRootExtension.cs b/src/Configuration/Config/src/InternalConfigurationRootExtensions.cs
similarity index 94%
rename from src/Configuration/Config/src/ConfigurationRootExtension.cs
rename to src/Configuration/Config/src/InternalConfigurationRootExtensions.cs
index 08366048a29..e03aaa31b9d 100644
--- a/src/Configuration/Config/src/ConfigurationRootExtension.cs
+++ b/src/Configuration/Config/src/InternalConfigurationRootExtensions.cs
@@ -7,7 +7,7 @@ namespace Microsoft.Extensions.Configuration
///
/// Extensions method for
///
- internal static class ConfigurationRootExtension
+ internal static class InternalConfigurationRootExtensions
{
///
/// Gets the immediate children sub-sections of configuration root based on key.
diff --git a/src/Configuration/Config/test/ConfigurationProviderMemoryTest.cs b/src/Configuration/Config/test/ConfigurationProviderMemoryTest.cs
index 8d15ed83d1b..3d81b57c4eb 100644
--- a/src/Configuration/Config/test/ConfigurationProviderMemoryTest.cs
+++ b/src/Configuration/Config/test/ConfigurationProviderMemoryTest.cs
@@ -7,6 +7,11 @@ namespace Microsoft.Extensions.Configuration.Test
{
public class ConfigurationProviderMemoryTest : ConfigurationProviderTestBase
{
+ public override void Null_values_are_included_in_the_config()
+ {
+ AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true);
+ }
+
protected override (IConfigurationProvider Provider, Action Initializer) LoadThroughProvider(
TestSection testConfig)
=> LoadUsingMemoryProvider(testConfig);
diff --git a/src/Configuration/Config/test/ConfigurationProviderTestBase.cs b/src/Configuration/Config/test/ConfigurationProviderTestBase.cs
index c18bea8d285..4609ee2560f 100644
--- a/src/Configuration/Config/test/ConfigurationProviderTestBase.cs
+++ b/src/Configuration/Config/test/ConfigurationProviderTestBase.cs
@@ -14,7 +14,39 @@ public abstract class ConfigurationProviderTestBase
[Fact]
public virtual void Load_from_single_provider()
{
- AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig)));
+ var configRoot = BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig));
+
+ AssertConfig(configRoot);
+ }
+
+ [Fact]
+ public virtual void Has_debug_view()
+ {
+ var configRoot = BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig));
+ var providerTag = configRoot.Providers.Single().ToString();
+
+ var expected =
+ $@"Key1=Value1 ({providerTag})
+Section1:
+ Key2=Value12 ({providerTag})
+ Section2:
+ Key3=Value123 ({providerTag})
+ Key3a:
+ 0=ArrayValue0 ({providerTag})
+ 1=ArrayValue1 ({providerTag})
+ 2=ArrayValue2 ({providerTag})
+Section3:
+ Section4:
+ Key4=Value344 ({providerTag})
+";
+
+ AssertDebugView(configRoot, expected);
+ }
+
+ [Fact]
+ public virtual void Null_values_are_included_in_the_config()
+ {
+ AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true, nullValue: "");
}
[Fact]
@@ -22,8 +54,13 @@ public virtual void Combine_after_other_provider()
{
AssertConfig(
BuildConfigRoot(
- LoadUsingMemoryProvider(TestSection.MissingSection2Config),
+ LoadUsingMemoryProvider(TestSection.MissingSection2ValuesConfig),
LoadThroughProvider(TestSection.MissingSection4Config)));
+
+ AssertConfig(
+ BuildConfigRoot(
+ LoadUsingMemoryProvider(TestSection.MissingSection4Config),
+ LoadThroughProvider(TestSection.MissingSection2ValuesConfig)));
}
[Fact]
@@ -31,8 +68,13 @@ public virtual void Combine_before_other_provider()
{
AssertConfig(
BuildConfigRoot(
- LoadThroughProvider(TestSection.MissingSection2Config),
+ LoadThroughProvider(TestSection.MissingSection2ValuesConfig),
LoadUsingMemoryProvider(TestSection.MissingSection4Config)));
+
+ AssertConfig(
+ BuildConfigRoot(
+ LoadThroughProvider(TestSection.MissingSection4Config),
+ LoadUsingMemoryProvider(TestSection.MissingSection2ValuesConfig)));
}
[Fact]
@@ -128,48 +170,83 @@ public class Section4AsOptions
public string Key4 { get; set; }
}
- protected virtual void AssertConfig(IConfigurationRoot config)
+ protected virtual void AssertDebugView(
+ IConfigurationRoot config,
+ string expected)
+ {
+ string RemoveLineEnds(string source) => source.Replace("\n", "").Replace("\r", "");
+
+ var actual = config.GetDebugView();
+
+ Assert.Equal(
+ RemoveLineEnds(expected),
+ RemoveLineEnds(actual));
+ }
+
+ protected virtual void AssertConfig(
+ IConfigurationRoot config,
+ bool expectNulls = false,
+ string nullValue = null)
{
- Assert.Equal("Value1", config["Key1"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("Value12", config["Section1:Key2"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("Value123", config["Section1:Section2:Key3"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue0", config["Section1:Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue1", config["Section1:Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue2", config["Section1:Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("Value344", config["Section3:Section4:Key4"], StringComparer.InvariantCultureIgnoreCase);
+ var value1 = expectNulls ? nullValue : "Value1";
+ var value12 = expectNulls ? nullValue : "Value12";
+ var value123 = expectNulls ? nullValue : "Value123";
+ var arrayvalue0 = expectNulls ? nullValue : "ArrayValue0";
+ var arrayvalue1 = expectNulls ? nullValue : "ArrayValue1";
+ var arrayvalue2 = expectNulls ? nullValue : "ArrayValue2";
+ var value344 = expectNulls ? nullValue : "Value344";
+
+ Assert.Equal(value1, config["Key1"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value12, config["Section1:Key2"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value123, config["Section1:Section2:Key3"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue0, config["Section1:Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue1, config["Section1:Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue2, config["Section1:Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value344, config["Section3:Section4:Key4"], StringComparer.InvariantCultureIgnoreCase);
var section1 = config.GetSection("Section1");
- Assert.Equal("Value12", section1["Key2"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("Value123", section1["Section2:Key3"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue0", section1["Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue1", section1["Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue2", section1["Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value12, section1["Key2"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value123, section1["Section2:Key3"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue0, section1["Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue1, section1["Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue2, section1["Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section1", section1.Path, StringComparer.InvariantCultureIgnoreCase);
Assert.Null(section1.Value);
var section2 = config.GetSection("Section1:Section2");
- Assert.Equal("Value123", section2["Key3"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue0", section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue1", section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue2", section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value123, section2["Key3"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue0, section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue1, section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue2, section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section1:Section2", section2.Path, StringComparer.InvariantCultureIgnoreCase);
Assert.Null(section2.Value);
section2 = section1.GetSection("Section2");
- Assert.Equal("Value123", section2["Key3"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue0", section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue1", section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("ArrayValue2", section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value123, section2["Key3"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue0, section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue1, section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue2, section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section1:Section2", section2.Path, StringComparer.InvariantCultureIgnoreCase);
Assert.Null(section2.Value);
+ var section3a = section2.GetSection("Key3a");
+ Assert.Equal(arrayvalue0, section3a["0"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue1, section3a["1"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue2, section3a["2"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal("Section1:Section2:Key3a", section3a.Path, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Null(section3a.Value);
+
+ var section3 = config.GetSection("Section3");
+ Assert.Equal("Section3", section3.Path, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Null(section3.Value);
+
var section4 = config.GetSection("Section3:Section4");
- Assert.Equal("Value344", section4["Key4"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value344, section4["Key4"], StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section3:Section4", section4.Path, StringComparer.InvariantCultureIgnoreCase);
Assert.Null(section4.Value);
section4 = config.GetSection("Section3").GetSection("Section4");
- Assert.Equal("Value344", section4["Key4"], StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value344, section4["Key4"], StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section3:Section4", section4.Path, StringComparer.InvariantCultureIgnoreCase);
Assert.Null(section4.Value);
@@ -179,7 +256,7 @@ protected virtual void AssertConfig(IConfigurationRoot config)
Assert.Equal("Key1", sections[0].Key, StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Key1", sections[0].Path, StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("Value1", sections[0].Value, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value1, sections[0].Value, StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section1", sections[1].Key, StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section1", sections[1].Path, StringComparer.InvariantCultureIgnoreCase);
@@ -195,11 +272,55 @@ protected virtual void AssertConfig(IConfigurationRoot config)
Assert.Equal("Key2", sections[0].Key, StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section1:Key2", sections[0].Path, StringComparer.InvariantCultureIgnoreCase);
- Assert.Equal("Value12", sections[0].Value, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value12, sections[0].Value, StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section2", sections[1].Key, StringComparer.InvariantCultureIgnoreCase);
Assert.Equal("Section1:Section2", sections[1].Path, StringComparer.InvariantCultureIgnoreCase);
Assert.Null(sections[1].Value);
+
+ sections = section2.GetChildren().ToList();
+
+ Assert.Equal(2, sections.Count);
+
+ Assert.Equal("Key3", sections[0].Key, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal("Section1:Section2:Key3", sections[0].Path, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value123, sections[0].Value, StringComparer.InvariantCultureIgnoreCase);
+
+ Assert.Equal("Key3a", sections[1].Key, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal("Section1:Section2:Key3a", sections[1].Path, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Null(sections[1].Value);
+
+ sections = section3a.GetChildren().ToList();
+
+ Assert.Equal(3, sections.Count);
+
+ Assert.Equal("0", sections[0].Key, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal("Section1:Section2:Key3a:0", sections[0].Path, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue0, sections[0].Value, StringComparer.InvariantCultureIgnoreCase);
+
+ Assert.Equal("1", sections[1].Key, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal("Section1:Section2:Key3a:1", sections[1].Path, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue1, sections[1].Value, StringComparer.InvariantCultureIgnoreCase);
+
+ Assert.Equal("2", sections[2].Key, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal("Section1:Section2:Key3a:2", sections[2].Path, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(arrayvalue2, sections[2].Value, StringComparer.InvariantCultureIgnoreCase);
+
+ sections = section3.GetChildren().ToList();
+
+ Assert.Single(sections);
+
+ Assert.Equal("Section4", sections[0].Key, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal("Section3:Section4", sections[0].Path, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Null(sections[0].Value);
+
+ sections = section4.GetChildren().ToList();
+
+ Assert.Single(sections);
+
+ Assert.Equal("Key4", sections[0].Key, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal("Section3:Section4:Key4", sections[0].Path, StringComparer.InvariantCultureIgnoreCase);
+ Assert.Equal(value344, sections[0].Value, StringComparer.InvariantCultureIgnoreCase);
}
protected abstract (IConfigurationProvider Provider, Action Initializer) LoadThroughProvider(TestSection testConfig);
@@ -272,7 +393,7 @@ public TestKeyValue(string[] values)
public IEnumerable<(string Key, string Value)> Expand(string key)
{
- if (AsString != null)
+ if (AsArray == null)
{
yield return (key, AsString);
}
@@ -310,7 +431,7 @@ protected class TestSection
Values = new[]
{
("Key3", (TestKeyValue)"Value123"),
- ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}),
+ ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"})
},
})
}
@@ -321,11 +442,10 @@ protected class TestSection
{
("Section4", new TestSection
{
- Values = new[] {("Key4", (TestKeyValue)"Value344")},
+ Values = new[] {("Key4", (TestKeyValue)"Value344")}
})
}
- }),
-
+ })
}
};
@@ -345,7 +465,7 @@ protected class TestSection
Values = new[]
{
("Key3", (TestKeyValue)"-----"),
- ("Key3a", (TestKeyValue)new[] {"-----------", "-----------", "-----------"}),
+ ("Key3a", (TestKeyValue)new[] {"-----------", "-----------", "-----------"})
},
})
}
@@ -356,15 +476,14 @@ protected class TestSection
{
("Section4", new TestSection
{
- Values = new[] {("Key4", (TestKeyValue)"--------")},
+ Values = new[] {("Key4", (TestKeyValue)"--------")}
})
}
- }),
-
+ })
}
};
- public static TestSection MissingSection2Config { get; }
+ public static TestSection MissingSection2ValuesConfig { get; }
= new TestSection
{
Values = new[] { ("Key1", (TestKeyValue)"Value1") },
@@ -373,6 +492,16 @@ protected class TestSection
("Section1", new TestSection
{
Values = new[] {("Key2", (TestKeyValue)"Value12")},
+ Sections = new[]
+ {
+ ("Section2", new TestSection
+ {
+ Values = new[]
+ {
+ ("Key3a", (TestKeyValue)new[] {"ArrayValue0"})
+ },
+ })
+ }
}),
("Section3", new TestSection
{
@@ -380,11 +509,10 @@ protected class TestSection
{
("Section4", new TestSection
{
- Values = new[] {("Key4", (TestKeyValue)"Value344")},
+ Values = new[] {("Key4", (TestKeyValue)"Value344")}
})
}
- }),
-
+ })
}
};
@@ -405,15 +533,12 @@ protected class TestSection
Values = new[]
{
("Key3", (TestKeyValue)"Value123"),
- ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}),
+ ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"})
},
})
}
}),
- ("Section3", new TestSection
- {
- }),
-
+ ("Section3", new TestSection())
}
};
@@ -433,7 +558,7 @@ protected class TestSection
Values = new[]
{
("KeY3", (TestKeyValue)"Value123"),
- ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}),
+ ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"})
},
})
}
@@ -444,11 +569,10 @@ protected class TestSection
{
("SectioN4", new TestSection
{
- Values = new[] {("KeY4", (TestKeyValue)"Value344")},
+ Values = new[] {("KeY4", (TestKeyValue)"Value344")}
})
}
- }),
-
+ })
}
};
@@ -472,7 +596,7 @@ protected class TestSection
Values = new[]
{
("Key3", (TestKeyValue)"Value123"),
- ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}),
+ ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"})
},
}),
("Section2", new TestSection
@@ -480,7 +604,7 @@ protected class TestSection
Values = new[]
{
("Key3", (TestKeyValue)"Value123"),
- ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}),
+ ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"})
},
})
@@ -492,11 +616,10 @@ protected class TestSection
{
("Section4", new TestSection
{
- Values = new[] {("Key4", (TestKeyValue)"Value344")},
+ Values = new[] {("Key4", (TestKeyValue)"Value344")}
})
}
- }),
-
+ })
}
};
@@ -520,7 +643,7 @@ protected class TestSection
Values = new[]
{
("Key3", (TestKeyValue)"Value123"),
- ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}),
+ ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"})
},
}),
("SectioN2", new TestSection
@@ -528,7 +651,7 @@ protected class TestSection
Values = new[]
{
("KeY3", (TestKeyValue)"Value123"),
- ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}),
+ ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"})
},
})
@@ -540,11 +663,97 @@ protected class TestSection
{
("Section4", new TestSection
{
- Values = new[] {("Key4", (TestKeyValue)"Value344")},
+ Values = new[] {("Key4", (TestKeyValue)"Value344")}
+ })
+ }
+ })
+ }
+ };
+
+ public static TestSection NullsTestConfig { get; }
+ = new TestSection
+ {
+ Values = new[] { ("Key1", new TestKeyValue((string)null)) },
+ Sections = new[]
+ {
+ ("Section1", new TestSection
+ {
+ Values = new[] {("Key2", new TestKeyValue((string)null))},
+ Sections = new[]
+ {
+ ("Section2", new TestSection
+ {
+ Values = new[]
+ {
+ ("Key3", new TestKeyValue((string)null)),
+ ("Key3a", (TestKeyValue)new string[] {null, null, null})
+ },
})
}
}),
+ ("Section3", new TestSection
+ {
+ Sections = new[]
+ {
+ ("Section4", new TestSection
+ {
+ Values = new[] {("Key4", new TestKeyValue((string)null))}
+ })
+ }
+ })
+ }
+ };
+ public static TestSection ExtraValuesTestConfig { get; }
+ = new TestSection
+ {
+ Values = new[]
+ {
+ ("Key1", (TestKeyValue)"Value1"),
+ ("Key1r", (TestKeyValue)"Value1r")
+ },
+ Sections = new[]
+ {
+ ("Section1", new TestSection
+ {
+ Values = new[]
+ {
+ ("Key2", (TestKeyValue)"Value12"),
+ ("Key2r", (TestKeyValue)"Value12r")
+ },
+ Sections = new[]
+ {
+ ("Section2", new TestSection
+ {
+ Values = new[]
+ {
+ ("Key3", (TestKeyValue)"Value123"),
+ ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2", "ArrayValue2r"}),
+ ("Key3ar", (TestKeyValue)new[] {"ArrayValue0r"})
+ },
+ })
+ }
+ }),
+ ("Section3", new TestSection
+ {
+ Sections = new[]
+ {
+ ("Section4", new TestSection
+ {
+ Values = new[] {("Key4", (TestKeyValue)"Value344")}
+ })
+ }
+ }),
+ ("Section5r", new TestSection
+ {
+ Sections = new[]
+ {
+ ("Section6r", new TestSection
+ {
+ Values = new[] {("Key5r", (TestKeyValue)"Value565r")}
+ })
+ }
+ })
}
};
}