From 885259a610d6cf7b114b1a3a68ca4d0520a4532b Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 31 Mar 2026 19:26:29 +0200 Subject: [PATCH 1/5] draft --- .../README.md | 7 +++- ...t.Extensions.Configuration.Abstractions.cs | 5 +++ .../src/ConfigurationIgnoreAttribute.cs | 15 ++++++++ .../src/PACKAGE.md | 7 +++- .../ConfigurationBindingGenerator.Parser.cs | 7 ++-- .../gen/Parser/KnownTypeSymbols.cs | 2 ++ .../gen/Specs/Members/PropertySpec.cs | 2 ++ .../gen/Specs/TypeIndex.cs | 2 +- .../src/ConfigurationBinder.cs | 20 +++++++++++ .../ConfigurationBinderTests.Helpers.cs | 3 ++ .../tests/Common/ConfigurationBinderTests.cs | 36 +++++++++++++++++++ 11 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/ConfigurationIgnoreAttribute.cs diff --git a/src/libraries/Microsoft.Extensions.Configuration.Abstractions/README.md b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/README.md index 0f78d178efa217..ec167d209ba481 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Abstractions/README.md +++ b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/README.md @@ -6,13 +6,16 @@ Documentation can be found at https://learn.microsoft.com/dotnet/core/extensions ## Example -The example below shows a small code sample using this library and trying out the `ConfigurationKeyName` attribute available since .NET 6: +The example below shows a small code sample using this library and trying out the `ConfigurationKeyName` and `ConfigurationIgnore` attributes: ```cs public class MyClass { [ConfigurationKeyName("named_property")] public string NamedProperty { get; set; } + + [ConfigurationIgnore] + public string IgnoredProperty { get; set; } = "default"; } ``` @@ -22,6 +25,7 @@ Given the simple class above, we can create a dictionary to hold the configurati var dic = new Dictionary { {"named_property", "value for named property"}, + {"ignored_property", "this value is ignored"}, }; var config = new ConfigurationBuilder() @@ -30,6 +34,7 @@ var config = new ConfigurationBuilder() var options = config.Get(); Console.WriteLine(options.NamedProperty); // returns "value for named property" +Console.WriteLine(options.IgnoredProperty); // returns "default" ``` ## Contribution Bar diff --git a/src/libraries/Microsoft.Extensions.Configuration.Abstractions/ref/Microsoft.Extensions.Configuration.Abstractions.cs b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/ref/Microsoft.Extensions.Configuration.Abstractions.cs index 03f00e5749e9b9..c3eeb7e961f804 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Abstractions/ref/Microsoft.Extensions.Configuration.Abstractions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/ref/Microsoft.Extensions.Configuration.Abstractions.cs @@ -26,6 +26,11 @@ public static partial class ConfigurationExtensions public static Microsoft.Extensions.Configuration.IConfigurationSection GetRequiredSection(this Microsoft.Extensions.Configuration.IConfiguration configuration, string key) { throw null; } } [System.AttributeUsageAttribute(System.AttributeTargets.Property)] + public sealed partial class ConfigurationIgnoreAttribute : System.Attribute + { + public ConfigurationIgnoreAttribute() { } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Property)] public sealed partial class ConfigurationKeyNameAttribute : System.Attribute { public ConfigurationKeyNameAttribute(string name) { } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/ConfigurationIgnoreAttribute.cs b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/ConfigurationIgnoreAttribute.cs new file mode 100644 index 00000000000000..1a4b4eaecc5312 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/ConfigurationIgnoreAttribute.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Extensions.Configuration +{ + /// + /// Specifies that a configuration property should be excluded from binding. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class ConfigurationIgnoreAttribute : Attribute + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/PACKAGE.md b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/PACKAGE.md index e744e1b9e73df1..05df30a857b445 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/PACKAGE.md +++ b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/PACKAGE.md @@ -17,13 +17,16 @@ Provides abstractions of key-value pair based configuration. Interfaces defined -The example below shows a small code sample using this library and trying out the `ConfigurationKeyName` attribute available since .NET 6: +The example below shows a small code sample using this library and trying out the `ConfigurationKeyName` and `ConfigurationIgnore` attributes: ```cs public class MyClass { [ConfigurationKeyName("named_property")] public string NamedProperty { get; set; } + + [ConfigurationIgnore] + public string IgnoredProperty { get; set; } = "default"; } ``` @@ -33,6 +36,7 @@ Given the simple class above, we can create a dictionary to hold the configurati var dic = new Dictionary { {"named_property", "value for named property"}, + {"ignored_property", "this value is ignored"}, }; var config = new ConfigurationBuilder() @@ -41,6 +45,7 @@ var config = new ConfigurationBuilder() var options = config.Get(); Console.WriteLine(options.NamedProperty); // returns "value for named property" +Console.WriteLine(options.IgnoredProperty); // returns "default" ``` ## Main Types 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..75b73075cd3b45 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -723,13 +723,16 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) } TypeRef propertyTypeRef = EnqueueTransitiveType(typeParseInfo, property.Type, DiagnosticDescriptors.PropertyNotSupported, propertyName); + ImmutableArray attributes = property.GetAttributes(); - AttributeData? attributeData = property.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationKeyNameAttribute)); + AttributeData? attributeData = attributes.FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationKeyNameAttribute)); string configKeyName = attributeData?.ConstructorArguments.FirstOrDefault().Value as string ?? propertyName; + bool isIgnored = attributes.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationIgnoreAttribute)); PropertySpec spec = new(property, propertyTypeRef) { - ConfigurationKeyName = configKeyName + ConfigurationKeyName = configKeyName, + IsIgnored = isIgnored, }; (properties ??= new(StringComparer.OrdinalIgnoreCase))[propertyName] = spec; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs index 7eba00408d053a..5b60ff7f148093 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs @@ -30,6 +30,7 @@ internal sealed class KnownTypeSymbols public INamedTypeSymbol? ActionOfBinderOptions { get; } public INamedTypeSymbol? ConfigurationBinder { get; } + public INamedTypeSymbol? ConfigurationIgnoreAttribute { get; } public INamedTypeSymbol? ConfigurationKeyNameAttribute { get; } public INamedTypeSymbol? OptionsBuilderConfigurationExtensions { get; } public INamedTypeSymbol? OptionsBuilderOfT { get; } @@ -88,6 +89,7 @@ public KnownTypeSymbols(CSharpCompilation compilation) INamedTypeSymbol? binderOptions = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.BinderOptions"); ActionOfBinderOptions = binderOptions is null ? null : compilation.GetBestTypeByMetadataName(typeof(Action<>))?.Construct(binderOptions); ConfigurationBinder = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.ConfigurationBinder"); + ConfigurationIgnoreAttribute = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.ConfigurationIgnoreAttribute"); ConfigurationKeyNameAttribute = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.ConfigurationKeyNameAttribute"); IConfiguration = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.IConfiguration"); IConfigurationSection = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.IConfigurationSection"); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs index 66257d06cab891..79a2d8f5f904c1 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs @@ -22,6 +22,8 @@ public PropertySpec(IPropertySymbol property, TypeRef typeRef) : base(property, public ParameterSpec? MatchingCtorParam { get; set; } + public bool IsIgnored { get; init; } + public bool IsStatic { get; } public bool SetOnInit { get; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs index 74ab042c2a7969..029c6e848c7c1f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs @@ -40,7 +40,7 @@ public bool HasBindableMembers(ComplexTypeSpec typeSpec) => public bool ShouldBindTo(PropertySpec property) { TypeSpec propTypeSpec = GetEffectiveTypeSpec(property.TypeRef); - return IsAccessible() && !IsCollectionAndCannotOverride() && !IsDictWithUnsupportedKey(); + return IsAccessible() && !property.IsIgnored && !IsCollectionAndCannotOverride() && !IsDictWithUnsupportedKey(); bool IsAccessible() => property.CanGet || property.CanSet; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index fe864425dfef12..7b3398cb118125 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -245,6 +245,11 @@ private static void BindProperties(object instance, IConfiguration configuration foreach (PropertyInfo property in modelProperties) { + if (IsIgnoredProperty(property)) + { + continue; + } + if (constructorParameters is null || !constructorParameters.Any(p => p.Name == property.Name)) { BindProperty(property, instance, configuration, options); @@ -1162,6 +1167,21 @@ private static List GetAllProperties( return propertyBindingPoint.Value; } + private static bool IsIgnoredProperty(PropertyInfo property) + { + ArgumentNullException.ThrowIfNull(property); + + foreach (CustomAttributeData attributeData in property.GetCustomAttributesData()) + { + if (attributeData.AttributeType == typeof(ConfigurationIgnoreAttribute)) + { + return true; + } + } + + return false; + } + private static string GetPropertyName(PropertyInfo property) { ArgumentNullException.ThrowIfNull(property); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Helpers.cs index 488e9b81c99d1f..0d8976bb00de5a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Helpers.cs @@ -64,6 +64,9 @@ public ComplexOptions() [ConfigurationKeyName("Named_Property")] public string NamedProperty { get; set; } + [ConfigurationIgnore] + public string IgnoredProperty { get; set; } = "Default"; + protected string ProtectedPrivateSet { get; private set; } private string PrivateReadOnly { get; } 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 a652e96983265d..b5952540998e45 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -199,6 +199,22 @@ public void CanBindConfigurationKeyNameAttributes() Assert.Equal("Yo", options.NamedProperty); } + [Fact] + public void CanIgnoreConfigurationAttributes() + { + var dic = new Dictionary + { + {"IgnoredProperty", "Ignored"}, + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + var options = config.Get(); + + Assert.Equal("Default", options.IgnoredProperty); + } + [Fact] public void EmptyStringIsNullable() { @@ -497,6 +513,26 @@ public void DoesNotThrowWhenConfigKeyMatchesConfigurationKeyNameAttribute() Assert.Equal("Yo", instance.NamedProperty); } + [Fact] + public void DoesNotThrowWhenConfigKeyMatchesConfigurationIgnoreAttribute() + { + var dic = new Dictionary + { + {"IgnoredProperty", "Ignored"}, + {"Integer", "-2"}, + {"Boolean", "TRUe"}, + {"Nested:Integer", "11"} + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + var instance = new ComplexOptions(); + config.Bind(instance, o => o.ErrorOnUnknownConfiguration = true); + + Assert.Equal("Default", instance.IgnoredProperty); + } + [Fact] public void GetDefaultsWhenDataDoesNotExist() { From bac23784138c7a1f941718d6f92b41e5fcf2247e Mon Sep 17 00:00:00 2001 From: rosebyte Date: Wed, 1 Apr 2026 08:18:54 +0200 Subject: [PATCH 2/5] cover constructor flows --- .../ConfigurationBindingGenerator.Parser.cs | 2 +- .../src/ConfigurationBinder.cs | 5 +++++ .../ConfigurationBinderTests.TestClasses.cs | 7 +++++++ .../tests/Common/ConfigurationBinderTests.cs | 19 +++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) 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 75b73075cd3b45..c6938e58cf19e9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -757,7 +757,7 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) { (missingParameters ??= new()).Add(parameterName); } - else if (parameter.RefKind is not RefKind.None) + else if (parameter.RefKind is not RefKind.None || propertySpec.IsIgnored) { (invalidParameters ??= new()).Add(parameterName); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index 7b3398cb118125..14ee14eefd820d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -627,6 +627,11 @@ private static bool DoAllParametersHaveEquivalentProperties(ParameterInfo[] para HashSet propertyNames = new(StringComparer.OrdinalIgnoreCase); foreach (PropertyInfo prop in properties) { + if (IsIgnoredProperty(prop)) + { + continue; + } + propertyNames.Add(prop.Name); } 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..a229d5420e3be1 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 @@ -157,6 +157,13 @@ public class ClassWithPrimaryCtor(string color, int length) public int Length { get; } = length; } + public class ClassWithPrimaryCtorAndIgnoredProperty(string color, int length) + { + [ConfigurationIgnore] + public string Color { get; } = color; + public int Length { get; } = length; + } + public class ClassWithPrimaryCtorDefaultValues(string color = "blue", int length = 15, decimal height = 5.946238490567943927384M, EditorBrowsableState eb = EditorBrowsableState.Never) { public string Color { get; } = color; 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 b5952540998e45..9fe79aeb22d8d8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -1482,6 +1482,25 @@ public void CanBindClassWithPrimaryCtor() Assert.Equal("Green", options.Color); } + [Fact] + public void ThrowOnClassWithPrimaryCtorAndIgnoredProperty() + { + var dic = new Dictionary + { + {"Length", "42"}, + {"Color", "Green"}, + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + var ex = Assert.Throws(() => config.Get()); + + Assert.Equal( + SR.Format(SR.Error_ConstructorParametersDoNotMatchProperties, typeof(ClassWithPrimaryCtorAndIgnoredProperty), "color"), + ex.Message); + } + [Fact] public void CanBindClassWithPrimaryCtorWithDefaultValues() { From f4a6f1e6a2a769575bb0f817c983fd4bc9a9c747 Mon Sep 17 00:00:00 2001 From: rosebyte Date: Thu, 9 Apr 2026 15:28:30 +0200 Subject: [PATCH 3/5] implement PR comment --- .../src/ConfigurationBinder.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index 14ee14eefd820d..d35a2b19998714 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -1172,20 +1172,7 @@ private static List GetAllProperties( return propertyBindingPoint.Value; } - private static bool IsIgnoredProperty(PropertyInfo property) - { - ArgumentNullException.ThrowIfNull(property); - - foreach (CustomAttributeData attributeData in property.GetCustomAttributesData()) - { - if (attributeData.AttributeType == typeof(ConfigurationIgnoreAttribute)) - { - return true; - } - } - - return false; - } + private static bool IsIgnoredProperty(PropertyInfo property) => property.IsDefined(typeof(ConfigurationIgnoreAttribute)); private static string GetPropertyName(PropertyInfo property) { From cf5e787ae53e4afd3d1c7ac1e0da74a0091538be Mon Sep 17 00:00:00 2001 From: rosebyte Date: Thu, 9 Apr 2026 18:22:22 +0200 Subject: [PATCH 4/5] fix parser behaviour --- .../gen/ConfigurationBindingGenerator.Parser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c6938e58cf19e9..53ffb12fcede11 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -753,11 +753,11 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) { string parameterName = parameter.Name; - if (properties?.TryGetValue(parameterName, out PropertySpec? propertySpec) is not true) + if (properties?.TryGetValue(parameterName, out PropertySpec? propertySpec) is not true || propertySpec.IsIgnored) { (missingParameters ??= new()).Add(parameterName); } - else if (parameter.RefKind is not RefKind.None || propertySpec.IsIgnored) + else if (parameter.RefKind is not RefKind.None) { (invalidParameters ??= new()).Add(parameterName); } From 6f8c2eab41781121db893eed0cba467c40a6fe7c Mon Sep 17 00:00:00 2001 From: rosebyte Date: Thu, 9 Apr 2026 18:22:30 +0200 Subject: [PATCH 5/5] add tests --- .../tests/Common/ConfigurationBinderTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) 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 9fe79aeb22d8d8..b16dcdea88766f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -533,6 +533,38 @@ public void DoesNotThrowWhenConfigKeyMatchesConfigurationIgnoreAttribute() Assert.Equal("Default", instance.IgnoredProperty); } + [Fact] + public void DoesNotThrowWhenConfigKeyMatchesReadOnlyPropertyWithErrorOnUnknownConfiguration() + { + var dic = new Dictionary + { + {"ReadOnly", "stuff"}, + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + var instance = new ComplexOptions(); + config.Bind(instance, o => o.ErrorOnUnknownConfiguration = true); + + Assert.Null(instance.ReadOnly); + } + + [Fact] + public void DoesNotThrowWhenConfigKeyMatchesSetOnlyPropertyWithErrorOnUnknownConfiguration() + { + var dic = new Dictionary + { + {"SetOnly", "42"}, + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + var options = config.Get(o => o.ErrorOnUnknownConfiguration = true); + Assert.False(options.AnyCalled); + } + [Fact] public void GetDefaultsWhenDataDoesNotExist() { @@ -1501,6 +1533,26 @@ public void ThrowOnClassWithPrimaryCtorAndIgnoredProperty() ex.Message); } + [Fact] + public void ThrowOnClassWithPrimaryCtorAndIgnoredPropertyWithUnknownConfigurationValidation() + { + var dic = new Dictionary + { + {"Length", "42"}, + {"Color", "Green"}, + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + var ex = Assert.Throws( + () => config.Get(o => o.ErrorOnUnknownConfiguration = true)); + + Assert.Equal( + SR.Format(SR.Error_ConstructorParametersDoNotMatchProperties, typeof(ClassWithPrimaryCtorAndIgnoredProperty), "color"), + ex.Message); + } + [Fact] public void CanBindClassWithPrimaryCtorWithDefaultValues() {