Skip to content

Commit 4d47635

Browse files
committed
Method to get source for each configuration element
Fixes #609 by allowing the configuration paths to be associated with values to help with debugging.
1 parent 8c833ae commit 4d47635

File tree

15 files changed

+420
-70
lines changed

15 files changed

+420
-70
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
8+
namespace Microsoft.Extensions.Configuration
9+
{
10+
/// <summary>
11+
/// Extension methods for <see cref="IConfigurationRoot"/>.
12+
/// </summary>
13+
public static class ConfigurationRootExtensions
14+
{
15+
/// <summary>
16+
/// Generates a human-readable view of the configuration showing where each value came from.
17+
/// </summary>
18+
/// <returns> The debug view. </returns>
19+
public static string GetDebugView(this IConfigurationRoot root)
20+
{
21+
void RecurseChildren(
22+
StringBuilder stringBuilder,
23+
IEnumerable<IConfigurationSection> children,
24+
string indent)
25+
{
26+
foreach (var child in children)
27+
{
28+
var valueAndProvider = GetValueAndProvider(root, child.Path);
29+
30+
if (valueAndProvider.Provider != null)
31+
{
32+
stringBuilder
33+
.Append(indent)
34+
.Append(child.Key)
35+
.Append("=")
36+
.Append(valueAndProvider.Value)
37+
.Append(" (")
38+
.Append(valueAndProvider.Provider)
39+
.AppendLine(")");
40+
}
41+
else
42+
{
43+
stringBuilder
44+
.Append(indent)
45+
.Append(child.Key)
46+
.AppendLine(":");
47+
}
48+
49+
RecurseChildren(stringBuilder, child.GetChildren(), indent + " ");
50+
}
51+
}
52+
53+
var builder = new StringBuilder();
54+
55+
RecurseChildren(builder, root.GetChildren(), "");
56+
57+
return builder.ToString();
58+
}
59+
60+
private static (string Value, IConfigurationProvider Provider) GetValueAndProvider(
61+
IConfigurationRoot root,
62+
string key)
63+
{
64+
foreach (var provider in root.Providers.Reverse())
65+
{
66+
if (provider.TryGet(key, out var value))
67+
{
68+
return (value, provider);
69+
}
70+
}
71+
72+
return (null, null);
73+
}
74+
}
75+
}

src/Configuration/Config.Abstractions/src/IConfigurationProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ public interface IConfigurationProvider
4747
/// <returns>The child keys.</returns>
4848
IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath);
4949
}
50-
}
50+
}

src/Configuration/Config.Abstractions/src/IConfigurationRoot.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System;
54
using System.Collections.Generic;
65

76
namespace Microsoft.Extensions.Configuration

src/Configuration/Config.AzureKeyVault/test/ConfigurationProviderAzureKeyVaultTest.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ namespace Microsoft.Extensions.Configuration.AzureKeyVault.Test
1414
{
1515
public class ConfigurationProviderKeyVaultTest : ConfigurationProviderTestBase
1616
{
17+
public override void Null_values_are_included_in_the_config()
18+
{
19+
AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true);
20+
}
21+
1722
protected override (IConfigurationProvider Provider, System.Action Initializer) LoadThroughProvider(
1823
TestSection testConfig)
1924
{

src/Configuration/Config.EnvironmentVariables/test/ConfigurationProviderEnvironmentVariablesTest.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,10 @@ public override void Load_from_single_provider_with_differing_case_duplicates_th
2626
{
2727
AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.DuplicatesDifferentCaseTestConfig)));
2828
}
29+
30+
public override void Null_values_are_included_in_the_config()
31+
{
32+
AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true);
33+
}
2934
}
3035
}

src/Configuration/Config.FileExtensions/src/FileConfigurationProvider.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ public FileConfigurationProvider(FileConfigurationSource source)
4242
/// The source settings for this provider.
4343
/// </summary>
4444
public FileConfigurationSource Source { get; }
45+
46+
/// <summary>
47+
/// Generates a string representing this provider name and relevant details.
48+
/// </summary>
49+
/// <returns> The configuration name. </returns>
50+
public override string ToString()
51+
=> $"{GetType().Name} for '{Source.Path}' ({(Source.Optional ? "Optional" : "Required")})";
4552

4653
private void Load(bool reload)
4754
{

src/Configuration/Config.Json/test/ConfigurationProviderJsonTest.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ protected override (IConfigurationProvider Provider, Action Initializer) LoadThr
3535

3636
private void SectionToJson(StringBuilder jsonBuilder, TestSection section)
3737
{
38+
string ValueToJson(object value) => value == null ? "null" : $"'{value}'";
39+
3840
jsonBuilder.AppendLine("{");
3941

4042
foreach (var tuple in section.Values)
4143
{
4244
jsonBuilder.AppendLine(tuple.Value.AsArray != null
43-
? $"'{tuple.Key}': [{string.Join(", ", tuple.Value.AsArray.Select(v => $"'{v}'"))}],"
44-
: $"'{tuple.Key}': '{tuple.Value.AsString}',");
45+
? $"'{tuple.Key}': [{string.Join(", ", tuple.Value.AsArray.Select(ValueToJson))}],"
46+
: $"'{tuple.Key}': {ValueToJson(tuple.Value.AsString)},");
4547
}
4648

4749
foreach (var tuple in section.Sections)

src/Configuration/Config.KeyPerFile/src/KeyPerFileConfigurationProvider.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,12 @@ public override void Load()
6868
}
6969
}
7070
}
71+
72+
/// <summary>
73+
/// Generates a string representing this provider name and relevant details.
74+
/// </summary>
75+
/// <returns> The configuration name. </returns>
76+
public override string ToString()
77+
=> $"{GetType().Name} ({(Source.Optional ? "Optional" : "Required")})";
7178
}
7279
}

src/Configuration/Config.KeyPerFile/test/KeyPerFileTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,9 @@ public Stream CreateReadStream()
304304
throw new InvalidOperationException("Cannot create stream from directory");
305305
}
306306

307-
return new MemoryStream(Encoding.UTF8.GetBytes(_contents));
307+
return _contents == null
308+
? new MemoryStream()
309+
: new MemoryStream(Encoding.UTF8.GetBytes(_contents));
308310
}
309311
}
310312
}

src/Configuration/Config.Xml/test/ConfigurationProviderXmlTest.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,42 @@ namespace Microsoft.Extensions.Configuration.Xml.Test
1010
{
1111
public class ConfigurationProviderXmlTest : ConfigurationProviderTestBase
1212
{
13+
public override void Combine_before_other_provider()
14+
{
15+
// Disabled test due to XML handling of empty section.
16+
}
17+
1318
public override void Combine_after_other_provider()
1419
{
1520
// Disabled test due to XML handling of empty section.
1621
}
1722

23+
public override void Has_debug_view()
24+
{
25+
var configRoot = BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig));
26+
var providerTag = configRoot.Providers.Single().ToString();
27+
28+
var expected =
29+
$@"Key1=Value1 ({providerTag})
30+
Section1:
31+
Key2=Value12 ({providerTag})
32+
Section2:
33+
Key3=Value123 ({providerTag})
34+
Key3a:
35+
0=ArrayValue0 ({providerTag})
36+
Name=0 ({providerTag})
37+
1=ArrayValue1 ({providerTag})
38+
Name=1 ({providerTag})
39+
2=ArrayValue2 ({providerTag})
40+
Name=2 ({providerTag})
41+
Section3:
42+
Section4:
43+
Key4=Value344 ({providerTag})
44+
";
45+
46+
AssertDebugView(configRoot, expected);
47+
}
48+
1849
protected override (IConfigurationProvider Provider, Action Initializer) LoadThroughProvider(TestSection testConfig)
1950
{
2051
var xmlBuilder = new StringBuilder();
@@ -36,7 +67,7 @@ private void SectionToXml(StringBuilder xmlBuilder, string sectionName, TestSect
3667

3768
foreach (var tuple in section.Values)
3869
{
39-
if (tuple.Value.AsString != null)
70+
if (tuple.Value.AsArray == null)
4071
{
4172
xmlBuilder.AppendLine($"<{tuple.Key}>{tuple.Value.AsString}</{tuple.Key}>");
4273
}

0 commit comments

Comments
 (0)