Skip to content

Commit 85ebc4d

Browse files
removed need for nesting in public api
1 parent 82c100d commit 85ebc4d

7 files changed

Lines changed: 155 additions & 53 deletions

File tree

README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,21 @@ Another example is when accessing an URL from code, where the URL is usually com
2626

2727
using TemplatedConfiguration;
2828

29-
/* ... */
29+
/* snip ... */
3030

3131
var config = new ConfigurationBuilder()
32-
.WithRecursiveTemplateSupport(
32+
// First set up defaults, for example using in memory
33+
.AddInMemoryCollection(Global.DefaultSettings)
34+
35+
// Then set up the 'overrides'.
36+
.AddIniFile("Config.ini")
37+
.AddCommandLine(args)
38+
.AddEnvironmentVariables()
39+
3340
// Wrap the configuration providers with the provider that supports templating
34-
builder => builder
35-
// First set up defaults
36-
// Then set up the 'overrides'.
37-
.AddIniFile("Config.ini")
38-
.AddCommandLine(args)
39-
.AddEnvironmentVariables()
40-
)
41+
.WithRecursiveTemplateSupport()
42+
43+
// Any provider added after this will NOT partake in the templating.
4144
.Build();
4245
```
4346

src/TemplatedConfiguration.Example/Program.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,19 @@ class Program
99
static void Main(string[] args)
1010
{
1111
var config = new ConfigurationBuilder()
12-
.WithRecursiveTemplateSupport(
12+
// First set up defaults
13+
.AddInMemoryCollection(Global.DefaultSettings)
14+
.AddInMemoryCollection(ComponentA.DefaultSettings)
15+
.AddInMemoryCollection(ComponentB.DefaultSettings)
1316

14-
// Wrap the configuration providers with the provider that supports templating
15-
builder => builder
16-
// First set up defaults
17-
.AddInMemoryCollection(Global.DefaultSettings)
18-
.AddInMemoryCollection(ComponentA.DefaultSettings)
19-
.AddInMemoryCollection(ComponentB.DefaultSettings)
17+
// Then set up the 'overrides'.
18+
.AddIniFile("Config.ini")
19+
.AddCommandLine(args)
20+
.AddEnvironmentVariables()
21+
22+
// Wrap the configuration providers with the provider that supports templating
23+
.WithRecursiveTemplateSupport()
2024

21-
// Then set up the 'overrides'.
22-
.AddIniFile("Config.ini")
23-
.AddCommandLine(args)
24-
.AddEnvironmentVariables()
25-
)
2625
.Build();
2726

2827
Console.WriteLine("Component A: Connection String: " +config.GetValue<string>("ComponentA.ConnectionString"));

src/TemplatedConfiguration.Tests/TemplatedConfigurationSourceTests.cs

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,27 @@ namespace TemplatedConfiguration.Tests
66
{
77
public class TemplatedConfigurationSourceTests
88
{
9-
private readonly IConfigurationRoot _configurationRoot;
109

1110
public TemplatedConfigurationSourceTests()
1211
{
13-
_configurationRoot = new ConfigurationBuilder()
12+
13+
}
14+
15+
[Theory]
16+
[InlineData("SettingWithoutTemplate")] // Correctly cased
17+
[InlineData("settingwithouttemplate")] // Proves Case Insensititivity
18+
public void Can_get_value_without_template(string key)
19+
{
20+
var configurationRoot = BuildConfigurationRootWithoutRebuilding();
21+
22+
var result = configurationRoot.GetValue<string>(key);
23+
24+
Assert.Equal("normal value", result);
25+
}
26+
27+
private static IConfigurationRoot BuildConfigurationRootWithoutRebuilding()
28+
{
29+
var configurationRoot = new ConfigurationBuilder()
1430
.WithRecursiveTemplateSupport(
1531
builder => builder
1632
.AddInMemoryCollection(new Dictionary<string, string>
@@ -25,37 +41,81 @@ public TemplatedConfigurationSourceTests()
2541
{"Recursive", "this is {Recursive}"},
2642
})
2743
).Build();
44+
return configurationRoot;
2845
}
2946

3047

3148
[Theory]
32-
[InlineData("SettingWithoutTemplate")] // Correctly cased
33-
[InlineData("settingwithouttemplate")] // Proves Case Insensititivity
34-
public void Can_get_value_without_template(string key)
49+
[InlineData("TemplatedSetting")] // Correctly cased
50+
[InlineData("templatedsetting")] // Proves Case Insensititivity on root template name
51+
[InlineData("CaseInsensitiveSetting")] // Proves Case Insensititivity on recursive template
52+
public void Can_get_templated_default_value(string key)
3553
{
36-
var result = _configurationRoot.GetValue<string>(key);
54+
var configurationRoot = BuildConfigurationRootWithoutRebuilding();
55+
var result = configurationRoot.GetValue<string>(key);
3756

38-
Assert.Equal("normal value", result);
57+
Assert.Equal("this comes from the default setting: '[default value]'", result);
3958
}
4059

60+
[Fact]
61+
public void Can_handle_loops_in_recursion()
62+
{
63+
var configurationRoot = BuildConfigurationRootWithoutRebuilding();
64+
var result = configurationRoot.GetValue<string>("Recursive");
65+
66+
Assert.Equal("this is {Recursive}", result);
67+
}
4168

4269
[Theory]
4370
[InlineData("TemplatedSetting")] // Correctly cased
4471
[InlineData("templatedsetting")] // Proves Case Insensititivity on root template name
4572
[InlineData("CaseInsensitiveSetting")] // Proves Case Insensititivity on recursive template
46-
public void Can_get_templated_default_value(string key)
73+
public void Can_add_templated_configuration_without_subbuilder(string key)
4774
{
48-
var result = _configurationRoot.GetValue<string>(key);
75+
var configurationRoot = new ConfigurationBuilder()
76+
.AddInMemoryCollection(new Dictionary<string, string>
77+
{
78+
{"SettingWithoutTemplate", "normal value"},
79+
{"DefaultSetting", "[default value]"},
80+
})
81+
.AddInMemoryCollection(new Dictionary<string, string>
82+
{
83+
{"TemplatedSetting", "this comes from the default setting: '{DefaultSetting}'"},
84+
{"CaseInsensitiveSetting", "this comes from the default setting: '{defaultsetting}'"},
85+
{"Recursive", "this is {Recursive}"},
86+
})
87+
.WithRecursiveTemplateSupport()
88+
.Build();
89+
90+
var result = configurationRoot.GetValue<string>(key);
4991

5092
Assert.Equal("this comes from the default setting: '[default value]'", result);
93+
5194
}
5295

5396
[Fact]
54-
public void Can_handle_loops_in_recursion()
97+
public void Build_is_only_called_once_on_source()
5598
{
56-
var result = _configurationRoot.GetValue<string>("Recursive");
99+
var fakeConfigSource = new FakeConfigSource();
100+
var configurationRoot = new ConfigurationBuilder()
101+
.Add(fakeConfigSource)
102+
.WithRecursiveTemplateSupport()
103+
.Build();
57104

58-
Assert.Equal("this is {Recursive}", result);
105+
Assert.Equal(1, fakeConfigSource.BuildCount);
106+
107+
}
108+
109+
private class FakeConfigSource : IConfigurationSource
110+
{
111+
public int BuildCount = 0;
112+
113+
public IConfigurationProvider Build(IConfigurationBuilder builder)
114+
{
115+
BuildCount++;
116+
return new FakeConfigurationProvider();
117+
}
59118
}
119+
private class FakeConfigurationProvider : ConfigurationProvider { }
60120
}
61121
}

src/TemplatedConfiguration/IConfigurationBuidlerExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,11 @@ public static IConfigurationBuilder WithRecursiveTemplateSupport(this IConfigura
1010
builder.Add(new TemplatedConfigurationSource(configurer));
1111
return builder;
1212
}
13+
14+
public static IConfigurationBuilder WithRecursiveTemplateSupport(this IConfigurationBuilder builder)
15+
{
16+
builder.Add(new TemplatedConfigurationSource(builder));
17+
return builder;
18+
}
1319
}
1420
}

src/TemplatedConfiguration/TemplatedConfiguration.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5+
<Version>0.1.0</Version>
56
</PropertyGroup>
67

78
<ItemGroup>

src/TemplatedConfiguration/TemplatedConfigurationProvider.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ public static string Replace(this string str, string oldValue, string newValue,
3434
public class TemplatedConfigurationProvider : IConfigurationProvider
3535
{
3636
private static readonly Regex _regex = new Regex(@"(\{[\w,\-,\.]*\})", RegexOptions.Compiled);
37-
private readonly IConfigurationRoot _source;
37+
public readonly IConfigurationRoot InnerConfiguration;
3838

3939
/// <summary>Initialize a new instance from the source.</summary>
40-
/// <param name="source">The source settings.</param>
41-
public TemplatedConfigurationProvider(IConfigurationRoot source)
40+
/// <param name="innerConfiguration">The source settings.</param>
41+
public TemplatedConfigurationProvider(IConfigurationRoot innerConfiguration)
4242
{
43-
if (source == null)
44-
throw new ArgumentNullException(nameof(source));
45-
_source = source;
43+
if (innerConfiguration == null)
44+
throw new ArgumentNullException(nameof(innerConfiguration));
45+
InnerConfiguration = innerConfiguration;
4646
}
4747

4848
public bool TryGet(string key, out string value)
@@ -52,7 +52,7 @@ public bool TryGet(string key, out string value)
5252

5353
private bool TryGetInternal(TemplatedSettingKey key, HashSet<TemplatedSettingKey> visited, out string value)
5454
{
55-
value = _source[key.Name];
55+
value = InnerConfiguration[key.Name];
5656
if (value == null)
5757
return false;
5858

@@ -82,22 +82,22 @@ private bool TryGetInternal(TemplatedSettingKey key, HashSet<TemplatedSettingKey
8282

8383
public void Set(string key, string value)
8484
{
85-
_source[key] = value;
85+
InnerConfiguration[key] = value;
8686
}
8787

8888
public IChangeToken GetReloadToken()
8989
{
90-
return _source.GetReloadToken();
90+
return InnerConfiguration.GetReloadToken();
9191
}
9292

9393
public void Load()
9494
{
95-
_source.Reload();
95+
InnerConfiguration.Reload();
9696
}
9797

9898
public IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string path)
9999
{
100-
return _source.Providers.Aggregate(Enumerable.Empty<string>(),
100+
return InnerConfiguration.Providers.Aggregate(Enumerable.Empty<string>(),
101101
(seed, source) =>
102102
source.GetChildKeys(seed, path)).Distinct().Select(
103103
key =>
Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,65 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using Microsoft.Extensions.Configuration;
35

46
namespace TemplatedConfiguration
57
{
68
public class TemplatedConfigurationSource : IConfigurationSource
79
{
8-
private readonly IConfigurationRoot _config;
10+
private readonly ConfigurationBuilder _innerConfigurationBuilder;
911

10-
public TemplatedConfigurationSource(Action<IConfigurationBuilder> configurer) : this(BuildConfigurationRoot(configurer))
12+
public TemplatedConfigurationSource(IConfigurationBuilder builder)
1113
{
14+
_innerConfigurationBuilder = new ConfigurationBuilder();
15+
for (int i = 0; i < builder.Sources.Count; i++)
16+
{
17+
var source = builder.Sources[i];
18+
if (source == this)
19+
{
20+
// We only care about providers before this provider. So stop looking after we've found it.
21+
break;
22+
}
23+
24+
// We wrap the providerSource into a proxied provider source, that caches the built output, so that we don't build 'twice'.
25+
var proxiedSource = new ProxiedProviderSource(source);
26+
builder.Sources[i] = proxiedSource;
27+
_innerConfigurationBuilder.Add(proxiedSource);
28+
}
29+
}
1230

31+
public TemplatedConfigurationSource(Action<IConfigurationBuilder> configurer)
32+
{
33+
_innerConfigurationBuilder = new ConfigurationBuilder();
34+
configurer(_innerConfigurationBuilder);
1335
}
1436

15-
private static IConfigurationRoot BuildConfigurationRoot(Action<IConfigurationBuilder> configurerer)
37+
38+
public IConfigurationProvider Build(IConfigurationBuilder builder)
1639
{
17-
var builder = new ConfigurationBuilder();
18-
configurerer(builder);
19-
return builder.Build();
40+
return new TemplatedConfigurationProvider(_innerConfigurationBuilder.Build());
2041
}
42+
}
2143

22-
public TemplatedConfigurationSource(IConfigurationRoot config)
44+
internal class ProxiedProviderSource : IConfigurationSource
45+
{
46+
private readonly IConfigurationSource _source;
47+
48+
private IConfigurationProvider _provider;
49+
50+
public ProxiedProviderSource(IConfigurationSource source)
2351
{
24-
_config = config;
52+
_source = source;
2553
}
2654

2755
public IConfigurationProvider Build(IConfigurationBuilder builder)
2856
{
29-
return new TemplatedConfigurationProvider(_config);
57+
if (_provider != null)
58+
return _provider;
59+
60+
return _provider = _source.Build(builder);
3061
}
3162
}
63+
64+
3265
}

0 commit comments

Comments
 (0)