Skip to content

Commit 934b6e7

Browse files
Copilotsvick
andcommitted
Support IConfiguration as a bindable property type in ConfigurationBinder
Co-authored-by: svick <287848+svick@users.noreply.github.com>
1 parent 73bb89c commit 934b6e7

6 files changed

Lines changed: 148 additions & 5 deletions

File tree

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ private TypeSpec CreateTypeSpec(TypeParseInfo typeParseInfo)
203203
{
204204
spec = new ConfigurationSectionSpec(type);
205205
}
206+
else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.IConfiguration))
207+
{
208+
spec = new ConfigurationSectionSpec(type) { IsIConfiguration = true };
209+
}
206210
else if (type is INamedTypeSymbol)
207211
{
208212
spec = CreateObjectSpec(typeParseInfo);

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ private void EmitGetCoreMethod()
114114
{
115115
case ParsableFromStringSpec stringParsableType:
116116
{
117-
EmitCastToIConfigurationSection();
117+
EmitCastToIConfigurationSectionOrThrow();
118118
119119
EmitStartBlock($"if ({Identifier.TryGetConfigurationValue}({Identifier.configuration}, {Identifier.key}: null, out string? {Identifier.value}))");
120120
EmitBindingLogic(
@@ -126,9 +126,14 @@ private void EmitGetCoreMethod()
126126
EmitEndBlock(); // End if-check for input type.
127127
}
128128
break;
129+
case ConfigurationSectionSpec { IsIConfiguration: true }:
130+
{
131+
_writer.WriteLine($"return {Identifier.configuration};");
132+
}
133+
break;
129134
case ConfigurationSectionSpec:
130135
{
131-
EmitCastToIConfigurationSection();
136+
EmitCastToIConfigurationSectionOrThrow();
132137
_writer.WriteLine($"return {Identifier.section};");
133138
}
134139
break;
@@ -161,7 +166,7 @@ private void EmitGetCoreMethod()
161166
EmitEndBlock();
162167
_emitBlankLineBeforeNextStatement = true;
163168

164-
void EmitCastToIConfigurationSection() =>
169+
void EmitCastToIConfigurationSectionOrThrow() =>
165170
_writer.WriteLine($$"""
166171
if ({{Identifier.configuration}} is not {{Identifier.IConfigurationSection}} {{Identifier.section}})
167172
{

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ public SimpleTypeSpec(ITypeSymbol type) : base(type) { }
1313
internal sealed record ConfigurationSectionSpec : SimpleTypeSpec
1414
{
1515
public ConfigurationSectionSpec(ITypeSymbol type) : base(type) { }
16+
17+
/// <summary>
18+
/// Indicates whether this spec represents <see cref="IConfiguration"/> (as opposed to <see cref="IConfigurationSection"/>).
19+
/// </summary>
20+
public bool IsIConfiguration { get; init; }
1621
}
1722

1823
public sealed record ParsableFromStringSpec : SimpleTypeSpec

src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,8 @@ private static void BindInstance(
324324
BinderOptions options,
325325
bool isParentCollection)
326326
{
327-
// if binding IConfigurationSection, break early
328-
if (type == typeof(IConfigurationSection))
327+
// if binding IConfigurationSection or IConfiguration, break early
328+
if (type == typeof(IConfigurationSection) || type == typeof(IConfiguration))
329329
{
330330
bindingPoint.TrySetValue(config);
331331
return;

src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,21 @@ public class ConfigurationInterfaceOptions
6565
public IConfigurationSection Section { get; set; }
6666
}
6767

68+
public class ConfigurationIConfigurationOptions
69+
{
70+
public IConfiguration Section { get; set; }
71+
}
72+
6873
public class DerivedOptionsWithIConfigurationSection : DerivedOptions
6974
{
7075
public IConfigurationSection DerivedSection { get; set; }
7176
}
7277

78+
public class DerivedOptionsWithIConfiguration : DerivedOptions
79+
{
80+
public IConfiguration DerivedSection { get; set; }
81+
}
82+
7383
public record struct RecordStructTypeOptions(string Color, int Length);
7484

7585
public record RecordOptionsWithNesting(int Number, RecordOptionsWithNesting.RecordNestedOptions Nested1,

src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,87 @@ void Test()
124124
}
125125
}
126126

127+
[Fact]
128+
public void CanBindIConfiguration()
129+
{
130+
var dic = new Dictionary<string, string>
131+
{
132+
{"Section:Integer", "-2"},
133+
{"Section:Boolean", "TRUe"},
134+
{"Section:Nested:Integer", "11"},
135+
{"Section:Virtual", "Sup"}
136+
};
137+
var configurationBuilder = new ConfigurationBuilder();
138+
configurationBuilder.AddInMemoryCollection(dic);
139+
var config = configurationBuilder.Build();
140+
141+
var options = config.Get<ConfigurationIConfigurationOptions>();
142+
var childOptions = options.Section.Get<DerivedOptions>();
143+
Test();
144+
145+
options = (ConfigurationIConfigurationOptions)config.Get(typeof(ConfigurationIConfigurationOptions));
146+
childOptions = (DerivedOptions)options.Section.Get(typeof(DerivedOptions));
147+
Test();
148+
149+
options = config.Get<ConfigurationIConfigurationOptions>(options => { });
150+
childOptions = options.Section.Get<DerivedOptions>(options => { });
151+
Test();
152+
153+
options = (ConfigurationIConfigurationOptions)config.Get(typeof(ConfigurationIConfigurationOptions), options => { });
154+
childOptions = (DerivedOptions)options.Section.Get(typeof(DerivedOptions), options => { });
155+
Test();
156+
157+
void Test()
158+
{
159+
Assert.True(childOptions.Boolean);
160+
Assert.Equal(-2, childOptions.Integer);
161+
Assert.Equal(11, childOptions.Nested.Integer);
162+
Assert.Equal("Derived:Sup", childOptions.Virtual);
163+
164+
var section = Assert.IsAssignableFrom<IConfigurationSection>(options.Section);
165+
Assert.Equal("Section", section.Key);
166+
Assert.Equal("Section", section.Path);
167+
Assert.Null(section.Value);
168+
}
169+
}
170+
171+
[Fact]
172+
public void CanBindIConfigurationWithDerivedOptionsSection()
173+
{
174+
var dic = new Dictionary<string, string>
175+
{
176+
{"Section:Integer", "-2"},
177+
{"Section:Boolean", "TRUe"},
178+
{"Section:Nested:Integer", "11"},
179+
{"Section:Virtual", "Sup"},
180+
{"Section:DerivedSection:Nested:Integer", "11"},
181+
{"Section:DerivedSection:Virtual", "Sup"}
182+
};
183+
var configurationBuilder = new ConfigurationBuilder();
184+
configurationBuilder.AddInMemoryCollection(dic);
185+
var config = configurationBuilder.Build();
186+
187+
var options = config.Get<ConfigurationIConfigurationOptions>();
188+
var childOptions = options.Section.Get<DerivedOptionsWithIConfiguration>();
189+
var childDerivedOptions = childOptions.DerivedSection.Get<DerivedOptions>();
190+
191+
Assert.True(childOptions.Boolean);
192+
Assert.Equal(-2, childOptions.Integer);
193+
Assert.Equal(11, childOptions.Nested.Integer);
194+
Assert.Equal("Derived:Sup", childOptions.Virtual);
195+
Assert.Equal(11, childDerivedOptions.Nested.Integer);
196+
Assert.Equal("Derived:Sup", childDerivedOptions.Virtual);
197+
198+
var section = Assert.IsAssignableFrom<IConfigurationSection>(options.Section);
199+
Assert.Equal("Section", section.Key);
200+
Assert.Equal("Section", section.Path);
201+
202+
var derivedSection = Assert.IsAssignableFrom<IConfigurationSection>(childOptions.DerivedSection);
203+
Assert.Equal("DerivedSection", derivedSection.Key);
204+
Assert.Equal("Section:DerivedSection", derivedSection.Path);
205+
Assert.Null(section.Value);
206+
}
207+
127208
[Fact]
128209
public void CanBindWithKeyOverload()
129210
{
@@ -2649,6 +2730,44 @@ static void ValidateList(List<IConfigurationSection> list)
26492730
}
26502731
}
26512732

2733+
[Fact]
2734+
public void GetIConfiguration()
2735+
{
2736+
var configuration = TestHelpers.GetConfigurationFromJsonString("""
2737+
{
2738+
"vaLue": "MyString",
2739+
}
2740+
""");
2741+
2742+
var obj = configuration.GetSection("value").Get<IConfiguration>();
2743+
var section = Assert.IsAssignableFrom<IConfigurationSection>(obj);
2744+
Assert.Equal("MyString", section.Value);
2745+
2746+
configuration = TestHelpers.GetConfigurationFromJsonString("""
2747+
{
2748+
"vaLue": [ "MyString", { "nested": "value" } ],
2749+
}
2750+
""");
2751+
2752+
var list = configuration.GetSection("value").Get<List<IConfiguration>>();
2753+
ValidateList(list);
2754+
2755+
var dict = configuration.Get<Dictionary<string, List<IConfiguration>>>();
2756+
Assert.Equal(1, dict.Count);
2757+
ValidateList(dict["vaLue"]);
2758+
2759+
static void ValidateList(List<IConfiguration> list)
2760+
{
2761+
Assert.Equal(2, list.Count);
2762+
Assert.Equal("0", Assert.IsAssignableFrom<IConfigurationSection>(list[0]).Key);
2763+
Assert.Equal("MyString", Assert.IsAssignableFrom<IConfigurationSection>(list[0]).Value);
2764+
2765+
Assert.Equal("1", Assert.IsAssignableFrom<IConfigurationSection>(list[1]).Key);
2766+
var nestedSection = Assert.IsAssignableFrom<IConfigurationSection>(list[1].GetSection("nested"));
2767+
Assert.Equal("value", nestedSection.Value);
2768+
}
2769+
}
2770+
26522771
[Fact]
26532772
public void NullableDictKeys()
26542773
{

0 commit comments

Comments
 (0)