diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs index beb45cff274be5..36b5e01ff42b07 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -203,6 +203,10 @@ private TypeSpec CreateTypeSpec(TypeParseInfo typeParseInfo) { spec = new ConfigurationSectionSpec(type); } + else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.IConfiguration)) + { + spec = new ConfigurationSectionSpec(type) { IsIConfiguration = true }; + } else if (type is INamedTypeSymbol) { spec = CreateObjectSpec(typeParseInfo); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs index 42979bf896c101..a4176e76233e45 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -126,6 +126,11 @@ private void EmitGetCoreMethod() EmitEndBlock(); // End if-check for input type. } break; + case ConfigurationSectionSpec { IsIConfiguration: true }: + { + _writer.WriteLine($"return {Identifier.configuration};"); + } + break; case ConfigurationSectionSpec: { EmitCastToIConfigurationSection(); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs index 70c7a8042e0359..6171e387c9b28e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs @@ -13,6 +13,11 @@ public SimpleTypeSpec(ITypeSymbol type) : base(type) { } internal sealed record ConfigurationSectionSpec : SimpleTypeSpec { public ConfigurationSectionSpec(ITypeSymbol type) : base(type) { } + + /// + /// Indicates whether this spec represents (as opposed to ). + /// + public bool IsIConfiguration { get; init; } } public sealed record ParsableFromStringSpec : SimpleTypeSpec diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index 8166b108da3f31..eb1b9c50da0997 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -324,8 +324,8 @@ private static void BindInstance( BinderOptions options, bool isParentCollection) { - // if binding IConfigurationSection, break early - if (type == typeof(IConfigurationSection)) + // if binding IConfigurationSection or IConfiguration, break early + if (type == typeof(IConfigurationSection) || type == typeof(IConfiguration)) { bindingPoint.TrySetValue(config); return; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs index 78f7cc9e27a554..1fbb3a7ef9172d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs @@ -60,16 +60,26 @@ public class NestedOptions } } - public class ConfigurationInterfaceOptions + public class OptionsWithIConfigurationSection { public IConfigurationSection Section { get; set; } } + public class OptionsWithIConfiguration + { + public IConfiguration Section { get; set; } + } + public class DerivedOptionsWithIConfigurationSection : DerivedOptions { public IConfigurationSection DerivedSection { get; set; } } + public class DerivedOptionsWithIConfiguration : DerivedOptions + { + public IConfiguration DerivedSection { get; set; } + } + public record struct RecordStructTypeOptions(string Color, int Length); public record RecordOptionsWithNesting(int Number, RecordOptionsWithNesting.RecordNestedOptions Nested1, diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs index 15514263abea49..d5dbe20ae7516b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -95,19 +95,19 @@ public void CanBindIConfigurationSection() configurationBuilder.AddInMemoryCollection(dic); var config = configurationBuilder.Build(); - var options = config.Get(); + var options = config.Get(); var childOptions = options.Section.Get(); Test(); - options = (ConfigurationInterfaceOptions)config.Get(typeof(ConfigurationInterfaceOptions)); + options = (OptionsWithIConfigurationSection)config.Get(typeof(OptionsWithIConfigurationSection)); childOptions = (DerivedOptions)options.Section.Get(typeof(DerivedOptions)); Test(); - options = config.Get(options => { }); + options = config.Get(options => { }); childOptions = options.Section.Get(options => { }); Test(); - options = (ConfigurationInterfaceOptions)config.Get(typeof(ConfigurationInterfaceOptions), options => { }); + options = (OptionsWithIConfigurationSection)config.Get(typeof(OptionsWithIConfigurationSection), options => { }); childOptions = (DerivedOptions)options.Section.Get(typeof(DerivedOptions), options => { }); Test(); @@ -125,7 +125,43 @@ void Test() } [Fact] - public void CanBindWithKeyOverload() + public void CanBindIConfigurationSectionWithDerivedOptionsSection() + { + var dic = new Dictionary + { + {"Section:Integer", "-2"}, + {"Section:Boolean", "TRUe"}, + {"Section:Nested:Integer", "11"}, + {"Section:Virtual", "Sup"}, + {"Section:DerivedSection:Nested:Integer", "11"}, + {"Section:DerivedSection:Virtual", "Sup"} + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + var options = config.Get(); + + var childOptions = options.Section.Get(); + + var childDerivedOptions = childOptions.DerivedSection.Get(); + + Assert.True(childOptions.Boolean); + Assert.Equal(-2, childOptions.Integer); + Assert.Equal(11, childOptions.Nested.Integer); + Assert.Equal("Derived:Sup", childOptions.Virtual); + Assert.Equal(11, childDerivedOptions.Nested.Integer); + Assert.Equal("Derived:Sup", childDerivedOptions.Virtual); + + Assert.Equal("Section", options.Section.Key); + Assert.Equal("Section", options.Section.Path); + Assert.Equal("DerivedSection", childOptions.DerivedSection.Key); + Assert.Equal("Section:DerivedSection", childOptions.DerivedSection.Path); + Assert.Null(options.Section.Value); + } + + [Fact] + public void CanBindIConfiguration() { var dic = new Dictionary { @@ -138,17 +174,38 @@ public void CanBindWithKeyOverload() configurationBuilder.AddInMemoryCollection(dic); var config = configurationBuilder.Build(); - var options = new DerivedOptions(); - config.Bind("Section", options); + var options = config.Get(); + var childOptions = options.Section.Get(); + Test(); - Assert.True(options.Boolean); - Assert.Equal(-2, options.Integer); - Assert.Equal(11, options.Nested.Integer); - Assert.Equal("Derived:Sup", options.Virtual); + options = (OptionsWithIConfiguration)config.Get(typeof(OptionsWithIConfiguration)); + childOptions = (DerivedOptions)options.Section.Get(typeof(DerivedOptions)); + Test(); + + options = config.Get(options => { }); + childOptions = options.Section.Get(options => { }); + Test(); + + options = (OptionsWithIConfiguration)config.Get(typeof(OptionsWithIConfiguration), options => { }); + childOptions = (DerivedOptions)options.Section.Get(typeof(DerivedOptions), options => { }); + Test(); + + void Test() + { + Assert.True(childOptions.Boolean); + Assert.Equal(-2, childOptions.Integer); + Assert.Equal(11, childOptions.Nested.Integer); + Assert.Equal("Derived:Sup", childOptions.Virtual); + + var section = Assert.IsAssignableFrom(options.Section); + Assert.Equal("Section", section.Key); + Assert.Equal("Section", section.Path); + Assert.Null(section.Value); + } } [Fact] - public void CanBindIConfigurationSectionWithDerivedOptionsSection() + public void CanBindIConfigurationWithDerivedOptionsSection() { var dic = new Dictionary { @@ -163,10 +220,8 @@ public void CanBindIConfigurationSectionWithDerivedOptionsSection() configurationBuilder.AddInMemoryCollection(dic); var config = configurationBuilder.Build(); - var options = config.Get(); - - var childOptions = options.Section.Get(); - + var options = config.Get(); + var childOptions = options.Section.Get(); var childDerivedOptions = childOptions.DerivedSection.Get(); Assert.True(childOptions.Boolean); @@ -176,11 +231,37 @@ public void CanBindIConfigurationSectionWithDerivedOptionsSection() Assert.Equal(11, childDerivedOptions.Nested.Integer); Assert.Equal("Derived:Sup", childDerivedOptions.Virtual); - Assert.Equal("Section", options.Section.Key); - Assert.Equal("Section", options.Section.Path); - Assert.Equal("DerivedSection", childOptions.DerivedSection.Key); - Assert.Equal("Section:DerivedSection", childOptions.DerivedSection.Path); - Assert.Null(options.Section.Value); + var section = Assert.IsAssignableFrom(options.Section); + Assert.Equal("Section", section.Key); + Assert.Equal("Section", section.Path); + + var derivedSection = Assert.IsAssignableFrom(childOptions.DerivedSection); + Assert.Equal("DerivedSection", derivedSection.Key); + Assert.Equal("Section:DerivedSection", derivedSection.Path); + Assert.Null(section.Value); + } + + [Fact] + public void CanBindWithKeyOverload() + { + var dic = new Dictionary + { + {"Section:Integer", "-2"}, + {"Section:Boolean", "TRUe"}, + {"Section:Nested:Integer", "11"}, + {"Section:Virtual", "Sup"} + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + var options = new DerivedOptions(); + config.Bind("Section", options); + + Assert.True(options.Boolean); + Assert.Equal(-2, options.Integer); + Assert.Equal(11, options.Nested.Integer); + Assert.Equal("Derived:Sup", options.Virtual); } [Fact] @@ -2621,6 +2702,8 @@ public void GetIConfigurationSection() } """); + Assert.Throws(() => configuration.Get()); + var obj = configuration.GetSection("value").Get(); Assert.Equal("MyString", obj.Value); @@ -2649,6 +2732,40 @@ static void ValidateList(List list) } } + [Fact] + public void GetIConfiguration() + { + var configuration = TestHelpers.GetConfigurationFromJsonString(""" + { + "vaLue": { "key": "MyString" }, + } + """); + + Assert.Same(configuration, configuration.Get()); + + var obj = configuration.GetSection("value").Get(); + Assert.Equal("MyString", obj["key"]); + + configuration = TestHelpers.GetConfigurationFromJsonString(""" + { + "vaLue": [ { "key": "MyString" } ], + } + """); + + var list = configuration.GetSection("value").Get>(); + ValidateList(list); + + var dict = configuration.Get>>(); + Assert.Equal(1, dict.Count); + ValidateList(dict["vaLue"]); + + static void ValidateList(List list) + { + Assert.Equal(1, list.Count); + Assert.Equal("MyString", list[0]["key"]); + } + } + [Fact] public void NullableDictKeys() {