diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index 2d2f5658188a4..bc7b74e24db8b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -204,7 +204,7 @@ public static void Bind(this IConfiguration configuration, object? instance, Act [RequiresUnreferencedCode(PropertyTrimmingWarningMessage)] private static void BindNonScalar(this IConfiguration configuration, object instance, BinderOptions options) { - List modelProperties = GetAllProperties(instance.GetType()); + PropertyInfo[] modelProperties = GetAllProperties(instance.GetType()); if (options.ErrorOnUnknownConfiguration) { @@ -451,7 +451,7 @@ private static object CreateInstance( } - List properties = GetAllProperties(type); + PropertyInfo[] properties = GetAllProperties(type); if (!DoAllParametersHaveEquivalentProperties(parameters, properties, out string nameOfInvalidParameters)) { @@ -482,7 +482,7 @@ private static object CreateInstance( } private static bool DoAllParametersHaveEquivalentProperties(ParameterInfo[] parameters, - List properties, out string missing) + PropertyInfo[] properties, out string missing) { HashSet propertyNames = new(StringComparer.OrdinalIgnoreCase); foreach (PropertyInfo prop in properties) @@ -752,22 +752,8 @@ private static bool IsArrayCompatibleReadOnlyInterface(Type type) return null; } - private static List GetAllProperties( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - Type type) - { - var allProperties = new List(); - - Type? baseType = type; - do - { - allProperties.AddRange(baseType!.GetProperties(DeclaredOnlyLookup)); - baseType = baseType.BaseType; - } - while (baseType != typeof(object)); - - return allProperties; - } + private static PropertyInfo[] GetAllProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type) + => type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); [RequiresUnreferencedCode(PropertyTrimmingWarningMessage)] private static object? BindParameter(ParameterInfo parameter, Type type, IConfiguration config, diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs index 932b6111fb25a..16d58c6166cec 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs @@ -1750,6 +1750,21 @@ public void CanBindNullableNestedStructProperties() Assert.True(bound.NullableNestedStruct.Value.DeeplyNested.Boolean); } + [Fact] + public void CanBindVirtualPropertiesWithoutDuplicates() + { + ConfigurationBuilder configurationBuilder = new(); + configurationBuilder.AddInMemoryCollection(new Dictionary + { + { "Test:0", "1" } + }); + IConfiguration config = configurationBuilder.Build(); + + var test = new ClassOverridingVirtualProperty(); + config.Bind(test); + Assert.Equal("1", Assert.Single(test.Test)); + } + private interface ISomeInterface { @@ -1827,5 +1842,15 @@ public struct DeeplyNested public bool Boolean { get; set; } } } + + public class BaseClassWithVirtualProperty + { + public virtual string[] Test { get; set; } = System.Array.Empty(); + } + + public class ClassOverridingVirtualProperty : BaseClassWithVirtualProperty + { + public override string[] Test { get => base.Test; set => base.Test = value; } + } } }