Skip to content

Commit 47f25fb

Browse files
[release/8.0-staging] Prefer most derived member in Configuration Binder source generator (#101686)
* Prefer most derived member in Configuration Binder source generator * Skip overridden properties in config source generator - include only definitions * Enable shipping Microsoft.Extensions.Configuration.Binder --------- Co-authored-by: Eric StJohn <[email protected]>
1 parent 8acc1b5 commit 47f25fb

File tree

4 files changed

+129
-2
lines changed

4 files changed

+129
-2
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,12 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo)
654654
if (member is IPropertySymbol { IsIndexer: false, IsImplicitlyDeclared: false } property && !IsUnsupportedType(property.Type))
655655
{
656656
string propertyName = property.Name;
657+
658+
if (property.IsOverride || properties?.ContainsKey(propertyName) is true)
659+
{
660+
continue;
661+
}
662+
657663
TypeRef propertyTypeRef = EnqueueTransitiveType(typeParseInfo, property.Type, DiagnosticDescriptors.PropertyNotSupported, propertyName);
658664

659665
AttributeData? attributeData = property.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationKeyNameAttribute));

src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
55
<EnableDefaultItems>true</EnableDefaultItems>
66
<IsPackable>true</IsPackable>
7-
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
8-
<ServicingVersion>1</ServicingVersion>
7+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
8+
<ServicingVersion>2</ServicingVersion>
99
<PackageDescription>Provides the functionality to bind an object to data in configuration providers for Microsoft.Extensions.Configuration. This package enables you to represent the configuration data as strongly-typed classes defined in the application code. To bind a configuration, use the Microsoft.Extensions.Configuration.ConfigurationBinder.Get extension method on the IConfiguration object. To use this package, you also need to install a package for the configuration provider, for example, Microsoft.Extensions.Configuration.Json for the JSON provider.</PackageDescription>
1010
</PropertyGroup>
1111

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,5 +930,64 @@ public class SimplePoco
930930
public string B { get; set; }
931931
}
932932

933+
public class BaseForHiddenMembers
934+
{
935+
public string A { get; set; }
936+
public string B { get; set; }
937+
public TestSettingsEnum E {get; set;}
938+
939+
public virtual string C { get => CBase; set => CBase = value; }
940+
941+
public string CBase;
942+
943+
public virtual string D { get; }
944+
945+
public virtual string F { get => FBase; set => FBase = value; }
946+
public string FBase;
947+
948+
949+
public virtual int X { get => XBase; set => XBase = value; }
950+
public int XBase;
951+
}
952+
953+
public enum TestSettingsEnum2
954+
{
955+
// Note - the reflection binder will try to bind to every member
956+
Option1 = TestSettingsEnum.Option1,
957+
Option2 = TestSettingsEnum.Option2,
958+
}
959+
960+
public class IntermediateDerivedClass : BaseForHiddenMembers
961+
{
962+
public new virtual string D { get => DBase; set => DBase = value; }
963+
public string DBase;
964+
965+
public override string F { get => "IF"; }
966+
967+
}
968+
969+
public class DerivedClassWithHiddenMembers : IntermediateDerivedClass
970+
{
971+
public new string A { get; } = "ADerived";
972+
public new int B { get; set; }
973+
public new TestSettingsEnum2 E
974+
{
975+
get => (TestSettingsEnum2)base.E;
976+
set => base.E = (TestSettingsEnum)value;
977+
}
978+
979+
// only override get
980+
public override string C { get => "DC"; }
981+
982+
// override new only get
983+
public override string D { get => "DD"; }
984+
985+
// two overrides of only get
986+
public override string F { get => "DF"; }
987+
988+
// override only set
989+
public override int X { set => base.X = value + 1; }
990+
}
991+
933992
}
934993
}

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2486,5 +2486,67 @@ public MockConfigurationRoot(IList<IConfigurationProvider> providers) : base(pro
24862486
IConfigurationSection IConfiguration.GetSection(string key) =>
24872487
this[key] is null ? null : new ConfigurationSection(this, key);
24882488
}
2489+
2490+
[Fact]
2491+
public void CanBindToClassWithNewProperties()
2492+
{
2493+
/// the source generator will bind to the most derived property only.
2494+
/// the reflection binder will bind the same data to all properties (including hidden).
2495+
2496+
var config = TestHelpers.GetConfigurationFromJsonString("""
2497+
{
2498+
"A": "AVal",
2499+
"B": "5",
2500+
"C": "CVal",
2501+
"D": "DVal",
2502+
"E": "Option2",
2503+
"F": "FVal",
2504+
"X": "52"
2505+
}
2506+
""");
2507+
var obj = new DerivedClassWithHiddenMembers();
2508+
2509+
config.Bind(obj);
2510+
2511+
BaseForHiddenMembers baseObj = obj;
2512+
IntermediateDerivedClass intermediateObj = obj;
2513+
2514+
Assert.Equal("ADerived", obj.A);
2515+
#if BUILDING_SOURCE_GENERATOR_TESTS
2516+
// source generator will not set hidden property
2517+
Assert.Null(baseObj.A);
2518+
#else
2519+
// reflection binder will set hidden property
2520+
Assert.Equal("AVal", baseObj.A);
2521+
#endif
2522+
2523+
Assert.Equal(5, obj.B);
2524+
#if BUILDING_SOURCE_GENERATOR_TESTS
2525+
// source generator will not set hidden property
2526+
Assert.Null(baseObj.B);
2527+
#else
2528+
// reflection binder will set hidden property
2529+
Assert.Equal("5", baseObj.B);
2530+
#endif
2531+
2532+
Assert.Equal(TestSettingsEnum2.Option2, obj.E);
2533+
Assert.Equal(TestSettingsEnum.Option2, baseObj.E);
2534+
2535+
Assert.Equal("DC", obj.C);
2536+
// The setter should still be called, even when only getter is overridden.
2537+
Assert.Equal("CVal", obj.CBase);
2538+
2539+
// can hide a readonly property with r/w property
2540+
Assert.Null(baseObj.D);
2541+
Assert.Equal("DD", obj.D);
2542+
// The setter should still be called, even when only getter is overridden.
2543+
Assert.Equal("DVal", obj.DBase);
2544+
2545+
Assert.Equal("DF", obj.F);
2546+
Assert.Equal("FVal", obj.FBase);
2547+
2548+
Assert.Equal(53, obj.X);
2549+
Assert.Equal(53, obj.XBase);
2550+
}
24892551
}
24902552
}

0 commit comments

Comments
 (0)