diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs index f242ca39d29991..f24a43c022ae43 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs @@ -15,17 +15,13 @@ private sealed partial class Emitter private readonly SourceProductionContext _context; private readonly SourceGenerationSpec _sourceGenSpec; - // Postfix for stringValueX variables used to save config value indexer - // results e.g. if (configuration["Key"] is string stringValue0) { ... } - private int _parseValueCount; - - private bool _precedingBlockExists; - - private readonly SourceWriter _writer = new(); + private bool _emitBlankLineBeforeNextStatement; + private bool _useFullyQualifiedNames; + private int _valueSuffixIndex; private static readonly Regex s_arrayBracketsRegex = new(Regex.Escape("[]")); - public bool _useFullyQualifiedNames { get; private set; } + private readonly SourceWriter _writer = new(); public Emitter(SourceProductionContext context, SourceGenerationSpec sourceGenSpec) { @@ -40,9 +36,13 @@ public void Emit() return; } - _writer.WriteLine(@"// -#nullable enable -"); + _writer.WriteBlock(""" + // + #nullable enable + #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + """); + _writer.WriteBlankLine(); + _useFullyQualifiedNames = true; EmitBinder_ConfigurationBinder(); EmitBinder_Extensions_OptionsBuilder(); @@ -54,119 +54,98 @@ public void Emit() _context.AddSource($"{Identifier.GeneratedConfigurationBinder}.g.cs", _writer.ToSourceText()); } - private void EmitBindLogicFromRootMethod(TypeSpec type, string expressionForMemberAccess, InitializationKind initKind) - { - TypeSpecKind kind = type.SpecKind; - - if (kind is TypeSpecKind.Nullable) - { - EmitBindLogicFromRootMethod(((NullableSpec)type).UnderlyingType, expressionForMemberAccess, initKind); - } - else - { - if (type is ParsableFromStringSpec stringParsableType) - { - if (initKind is InitializationKind.Declaration) - { - EmitCastToIConfigurationSection(); - _writer.WriteLine($"{GetTypeDisplayString(type)} {expressionForMemberAccess} = default!;"); - } - else - { - EmitCastToIConfigurationSection(); - } - - EmitBindLogicFromString(stringParsableType, Expression.sectionValue, Expression.sectionPath); - } - else - { - EmitBindCoreCall(type, expressionForMemberAccess, Identifier.configuration, initKind); - } - } - } - private void EmitBindCoreCall( TypeSpec type, - string expressionForMemberAccess, - string expressionForConfigArg, - InitializationKind initKind) + string memberAccessExpr, + string configArgExpr, + InitializationKind initKind, + Action? writeOnSuccess = null) { Debug.Assert(type.CanInitialize); - string tempVarName = GetIncrementalVarName(Identifier.temp); + if (!type.NeedsMemberBinding) + { + EmitObjectInit(memberAccessExpr, initKind); + return; + } + + string tempIdentifier = GetIncrementalIdentifier(Identifier.temp); if (initKind is InitializationKind.AssignmentWithNullCheck) { - _writer.WriteLine($"{type.MinimalDisplayString} {tempVarName} = {expressionForMemberAccess};"); - EmitObjectInit(type, tempVarName, InitializationKind.AssignmentWithNullCheck); - EmitBindCoreCall(tempVarName); + _writer.WriteLine($"{type.MinimalDisplayString} {tempIdentifier} = {memberAccessExpr};"); + EmitBindCoreCall(tempIdentifier, InitializationKind.AssignmentWithNullCheck); } else if (initKind is InitializationKind.None && type.IsValueType) { - EmitObjectInit(type, tempVarName, InitializationKind.Declaration); - _writer.WriteLine($@"{Identifier.BindCore}({expressionForConfigArg}, ref {tempVarName}, {Identifier.binderOptions});"); - _writer.WriteLine($"{expressionForMemberAccess} = {tempVarName};"); + EmitBindCoreCall(tempIdentifier, InitializationKind.Declaration); + _writer.WriteLine($"{memberAccessExpr} = {tempIdentifier};"); } else { - EmitObjectInit(type, expressionForMemberAccess, initKind); - EmitBindCoreCall(expressionForMemberAccess); + EmitBindCoreCall(memberAccessExpr, initKind); } - void EmitBindCoreCall(string varName) + void EmitBindCoreCall(string objExpression, InitializationKind initKind) { - string bindCoreCall = $@"{GetHelperMethodDisplayString(Identifier.BindCore)}({expressionForConfigArg}, ref {varName}, {Identifier.binderOptions});"; + string methodDisplayString = GetHelperMethodDisplayString(nameof(MethodsToGen_CoreBindingHelper.BindCore)); + string bindCoreCall = $@"{methodDisplayString}({configArgExpr}, ref {objExpression}, {Identifier.binderOptions});"; + + EmitObjectInit(objExpression, initKind); _writer.WriteLine(bindCoreCall); + writeOnSuccess?.Invoke(objExpression); + } + + void EmitObjectInit(string objExpression, InitializationKind initKind) + { + if (initKind is not InitializationKind.None) + { + this.EmitObjectInit(type, objExpression, initKind, configArgExpr); + } } } - public void EmitBindLogicFromString( + private void EmitBindLogicFromString( ParsableFromStringSpec type, - string configStringValueExpr, - string configValuePathExpr, - Action? writeOnSuccess = null, - bool isCollectionElement = false) + string sectionValueExpr, + string sectionPathExpr, + Action? writeOnSuccess, + bool checkForNullSectionValue, + bool useIncrementalStringValueIdentifier) { StringParsableTypeKind typeKind = type.StringParsableTypeKind; Debug.Assert(typeKind is not StringParsableTypeKind.None); - string stringValueVarName = GetIncrementalVarName(Identifier.stringValue); - string parsedValueExpr; + string nonNull_StringValue_Identifier = useIncrementalStringValueIdentifier ? GetIncrementalIdentifier(Identifier.value) : Identifier.value; + string stringValueToParse_Expr = checkForNullSectionValue ? nonNull_StringValue_Identifier : sectionValueExpr; - if (typeKind is StringParsableTypeKind.ConfigValue) + string parsedValueExpr; + if (typeKind is StringParsableTypeKind.AssignFromSectionValue) { - if (isCollectionElement) - { - parsedValueExpr = stringValueVarName; - } - else - { - writeOnSuccess?.Invoke(configStringValueExpr); - return; - } + parsedValueExpr = stringValueToParse_Expr; } else { string helperMethodDisplayString = GetHelperMethodDisplayString(type.ParseMethodName); - parsedValueExpr = $"{helperMethodDisplayString}({stringValueVarName}, () => {configValuePathExpr})"; + parsedValueExpr = $"{helperMethodDisplayString}({stringValueToParse_Expr}, () => {sectionPathExpr})"; } - _writer.WriteBlockStart($"if ({configStringValueExpr} is string {stringValueVarName})"); - writeOnSuccess?.Invoke(parsedValueExpr); - _writer.WriteBlockEnd(); - - return; + if (!checkForNullSectionValue) + { + writeOnSuccess?.Invoke(parsedValueExpr); + } + else + { + _writer.WriteBlockStart($"if ({sectionValueExpr} is string {nonNull_StringValue_Identifier})"); + writeOnSuccess?.Invoke(parsedValueExpr); + _writer.WriteBlockEnd(); + } } - private bool EmitObjectInit(TypeSpec type, string expressionForMemberAccess, InitializationKind initKind) + private bool EmitObjectInit(TypeSpec type, string memberAccessExpr, InitializationKind initKind, string configArgExpr) { - Debug.Assert(type.CanInitialize); - - if (initKind is InitializationKind.None) - { - return true; - } + Debug.Assert(type.CanInitialize && initKind is not InitializationKind.None); - string expressionForInit; + string initExpr; CollectionSpec? collectionType = type as CollectionSpec; string effectiveDisplayString = GetTypeDisplayString(type); @@ -174,30 +153,29 @@ private bool EmitObjectInit(TypeSpec type, string expressionForMemberAccess, Ini { if (collectionType is EnumerableSpec { InitializationStrategy: InitializationStrategy.Array }) { - expressionForInit = $"new {s_arrayBracketsRegex.Replace(effectiveDisplayString, "[0]", 1)}"; + initExpr = $"new {s_arrayBracketsRegex.Replace(effectiveDisplayString, "[0]", 1)}"; } else { effectiveDisplayString = GetTypeDisplayString(collectionType.ConcreteType ?? collectionType); - expressionForInit = $"new {effectiveDisplayString}()"; + initExpr = $"new {effectiveDisplayString}()"; } } else if (type.InitializationStrategy is InitializationStrategy.ParameterlessConstructor) { - expressionForInit = $"new {effectiveDisplayString}()"; + initExpr = $"new {effectiveDisplayString}()"; } else { Debug.Assert(type.InitializationStrategy is InitializationStrategy.ParameterizedConstructor); - string expressionForConfigSection = initKind is InitializationKind.Declaration ? Identifier.configuration : Identifier.section; - string initMethodIdentifier = GetHelperMethodDisplayString(((ObjectSpec)type).InitializeMethodDisplayString); - expressionForInit = $"{initMethodIdentifier}({expressionForConfigSection}, {Identifier.binderOptions});"; + string initMethodIdentifier = GetInitalizeMethodDisplayString(((ObjectSpec)type)); + initExpr = $"{initMethodIdentifier}({configArgExpr}, {Identifier.binderOptions})"; } if (initKind == InitializationKind.Declaration) { - Debug.Assert(!expressionForMemberAccess.Contains(".")); - _writer.WriteLine($"var {expressionForMemberAccess} = {expressionForInit};"); + Debug.Assert(!memberAccessExpr.Contains(".")); + _writer.WriteLine($"var {memberAccessExpr} = {initExpr};"); } else if (initKind == InitializationKind.AssignmentWithNullCheck) { @@ -208,28 +186,28 @@ private bool EmitObjectInit(TypeSpec type, string expressionForMemberAccess, Ini { if (collectionType.InitializationStrategy is InitializationStrategy.ParameterizedConstructor) { - _writer.WriteLine($"{expressionForMemberAccess} = {expressionForMemberAccess} is null ? {expressionForInit} : new {effectiveDisplayString}({expressionForMemberAccess});"); + _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr} is null ? {initExpr} : new {effectiveDisplayString}({memberAccessExpr});"); } else { - _writer.WriteLine($"{expressionForMemberAccess} = {expressionForMemberAccess} is null ? {expressionForInit} : {expressionForMemberAccess}.{collectionType.ToEnumerableMethodCall!};"); + _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr} is null ? {initExpr} : {memberAccessExpr}.{collectionType.ToEnumerableMethodCall!};"); } } else { - _writer.WriteLine($"{expressionForMemberAccess} ??= {expressionForInit};"); + _writer.WriteLine($"{memberAccessExpr} ??= {initExpr};"); } } else { Debug.Assert(initKind is InitializationKind.SimpleAssignment); - _writer.WriteLine($"{expressionForMemberAccess} = {expressionForInit};"); + _writer.WriteLine($"{memberAccessExpr} = {initExpr};"); } return true; } - public void EmitCastToIConfigurationSection() + private void EmitCastToIConfigurationSection() { string sectionTypeDisplayString; string exceptionTypeDisplayString; @@ -252,7 +230,7 @@ public void EmitCastToIConfigurationSection() """); } - public void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn) + private void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn) { string returnPostfix = voidReturn ? string.Empty : " null"; string methodDisplayString = GetHelperMethodDisplayString(Identifier.HasValueOrChildren); 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 41745204778bde..9127046f22c5e8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -99,18 +99,14 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || if (IsNullable(type, out ITypeSymbol? underlyingType)) { spec = TryGetTypeSpec(underlyingType, Diagnostics.NullableUnderlyingTypeNotSupported, out TypeSpec? underlyingTypeSpec) - ? new NullableSpec(type) { Location = location, UnderlyingType = underlyingTypeSpec } + ? new NullableSpec(type, underlyingTypeSpec) : null; } else if (IsParsableFromString(type, out StringParsableTypeKind specialTypeKind)) { - ParsableFromStringSpec stringParsableSpec = new(type) - { - Location = location, - StringParsableTypeKind = specialTypeKind - }; + ParsableFromStringSpec stringParsableSpec = new(type) { StringParsableTypeKind = specialTypeKind }; - if (stringParsableSpec.StringParsableTypeKind is not StringParsableTypeKind.ConfigValue) + if (stringParsableSpec.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) { _sourceGenSpec.PrimitivesForHelperGen.Add(stringParsableSpec); } @@ -119,17 +115,15 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || } else if (IsSupportedArrayType(type, location)) { - spec = CreateArraySpec((type as IArrayTypeSymbol)!, location); - RegisterBindCoreGenType(spec); + spec = CreateArraySpec((type as IArrayTypeSymbol)); } else if (IsCollection(type)) { spec = CreateCollectionSpec((INamedTypeSymbol)type, location); - RegisterBindCoreGenType(spec); } else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.IConfigurationSection)) { - spec = new ConfigurationSectionSpec(type) { Location = location }; + spec = new ConfigurationSectionSpec(type); } else if (type is INamedTypeSymbol namedType) { @@ -138,7 +132,6 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || _sourceGenSpec.TypeNamespaces.Add("System.Collections.Generic"); spec = CreateObjectSpec(namedType, location); - RegisterBindCoreGenType(spec); } if (spec is null) @@ -154,14 +147,6 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || } return _createdSpecs[type] = spec; - - void RegisterBindCoreGenType(TypeSpec? spec) - { - if (spec is not null) - { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec); - } - } } private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type) @@ -177,8 +162,11 @@ private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, Typ private void RegisterTypeForBindCoreUntypedGen(TypeSpec typeSpec) { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, typeSpec); - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreUntyped, typeSpec); + if (typeSpec.NeedsMemberBinding) + { + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, typeSpec); + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreUntyped, typeSpec); + } } private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType) @@ -222,7 +210,7 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t case SpecialType.System_String: case SpecialType.System_Object: { - typeKind = StringParsableTypeKind.ConfigValue; + typeKind = StringParsableTypeKind.AssignFromSectionValue; return true; } case SpecialType.System_Boolean: @@ -312,7 +300,7 @@ private bool TryGetTypeSpec(ITypeSymbol type, DiagnosticDescriptor descriptor, o return true; } - private EnumerableSpec? CreateArraySpec(IArrayTypeSymbol arrayType, Location? location) + private EnumerableSpec? CreateArraySpec(IArrayTypeSymbol arrayType) { if (!TryGetTypeSpec(arrayType.ElementType, Diagnostics.ElementTypeNotSupported, out TypeSpec elementSpec)) { @@ -325,7 +313,6 @@ private bool TryGetTypeSpec(ITypeSymbol type, DiagnosticDescriptor descriptor, o EnumerableSpec spec = new EnumerableSpec(arrayType) { - Location = location, ElementType = elementSpec, ConcreteType = listSpec, InitializationStrategy = InitializationStrategy.Array, @@ -334,6 +321,8 @@ private bool TryGetTypeSpec(ITypeSymbol type, DiagnosticDescriptor descriptor, o }; Debug.Assert(spec.CanInitialize); + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec); + return spec; } @@ -368,6 +357,7 @@ private bool IsSupportedArrayType(ITypeSymbol type, Location? location) if (spec is not null) { + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec); spec.InitExceptionMessage ??= spec.ElementType.InitExceptionMessage; } @@ -436,7 +426,6 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, Location? loc DictionarySpec spec = new(type) { - Location = location, KeyType = (ParsableFromStringSpec)keySpec, ElementType = elementSpec, InitializationStrategy = constructionStrategy, @@ -523,11 +512,10 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, Location? loc return null; } - RegisterHasChildrenHelperForGenIfRequired(elementSpec); + Register_AsConfigWithChildren_HelperForGen_IfRequired(elementSpec); EnumerableSpec spec = new(type) { - Location = location, ElementType = elementSpec, InitializationStrategy = constructionStrategy, PopulationStrategy = populationStrategy, @@ -544,7 +532,7 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, Location? loc private ObjectSpec? CreateObjectSpec(INamedTypeSymbol type, Location? location) { // Add spec to cache before traversing properties to avoid stack overflow. - ObjectSpec objectSpec = new(type) { Location = location }; + ObjectSpec objectSpec = new(type); _createdSpecs.Add(type, objectSpec); string typeName = objectSpec.Name; @@ -627,7 +615,7 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, Location? loc { PropertySpec spec = new(property) { Type = propertyTypeSpec, ConfigurationKeyName = configKeyName }; objectSpec.Properties[propertyName] = spec; - RegisterHasChildrenHelperForGenIfRequired(propertyTypeSpec); + Register_AsConfigWithChildren_HelperForGen_IfRequired(propertyTypeSpec); } } } @@ -691,17 +679,22 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, Location? loc Debug.Assert((objectSpec.CanInitialize && objectSpec.InitExceptionMessage is null) || (!objectSpec.CanInitialize && objectSpec.InitExceptionMessage is not null)); + if (objectSpec.NeedsMemberBinding) + { + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, objectSpec); + } + return objectSpec; } - private void RegisterHasChildrenHelperForGenIfRequired(TypeSpec type) + private void Register_AsConfigWithChildren_HelperForGen_IfRequired(TypeSpec type) { if (type.SpecKind is TypeSpecKind.Object or TypeSpecKind.Enumerable or TypeSpecKind.Dictionary) { - _sourceGenSpec.ShouldEmitHasChildren = true; + _sourceGenSpec.MethodsToGen_CoreBindingHelper |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs index a90eacd4837c52..d71e414af19dc7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs @@ -24,21 +24,20 @@ private void EmitBinder_ConfigurationBinder() return; } - _writer.WriteLine("/// Generated helper providing an AOT and linking compatible implementation for configuration binding."); - _writer.WriteBlockStart($"internal static class {Identifier.GeneratedConfigurationBinder}"); + _emitBlankLineBeforeNextStatement = false; + EmitRootBindingClassBlockStart(Identifier.GeneratedConfigurationBinder); EmitGetMethods(); EmitGetValueMethods(); EmitBindMethods_ConfigurationBinder(); _writer.WriteBlockEnd(); - - _precedingBlockExists = true; + _emitBlankLineBeforeNextStatement = true; } private void EmitGetMethods() { - const string expressionForGetCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{Identifier.GetCore}"; + const string expressionForGetCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.GetCore)}"; const string documentation = "Attempts to bind the configuration instance to a new instance of type T."; if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_T)) @@ -72,7 +71,7 @@ private void EmitGetMethods() private void EmitGetValueMethods() { - const string expressionForGetValueCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{Identifier.GetValueCore}"; + const string expressionForGetValueCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}"; const string documentation = "Extracts the value with the specified key and converts it to the specified type."; if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_T_key)) @@ -144,7 +143,7 @@ private void EmitBindMethods_ConfigurationBinder() EmitMethodImplementation( type, additionalParams: $"string {Identifier.key}, {GetObjParameter(type)}", - configExpression: $"{Identifier.configuration}.{Identifier.GetSection}({Identifier.key})", + configExpression: $"{Expression.configurationGetSection}({Identifier.key})", configureOptions: false); } } @@ -152,9 +151,18 @@ private void EmitBindMethods_ConfigurationBinder() void EmitMethodImplementation(TypeSpec type, string additionalParams, string configExpression, bool configureOptions) { string binderOptionsArg = configureOptions ? $"{Expression.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null"; - string returnExpression = type.CanInitialize - ? $"{FullyQualifiedDisplayString.CoreBindingHelper}.{Identifier.BindCore}({configExpression}, ref {Identifier.obj}, {binderOptionsArg})" - : GetInitException(type.InitExceptionMessage); + + string returnExpression; + if (type.CanInitialize) + { + returnExpression = type.NeedsMemberBinding + ? $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.BindCore)}({configExpression}, ref {Identifier.obj}, {binderOptionsArg})" + : "{ }"; + } + else + { + returnExpression = GetInitException(type.InitExceptionMessage); + } StartMethodDefinition("Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively."); _writer.WriteLine($"public static void {Identifier.Bind}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {additionalParams}) => " diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs index 04b811c3bc17d5..bdf56060b82dbe 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs @@ -18,18 +18,23 @@ private sealed partial class Emitter private void Emit_CoreBindingHelper() { - Debug.Assert(_precedingBlockExists); + Debug.Assert(_emitBlankLineBeforeNextStatement); _writer.WriteBlankLine(); - _precedingBlockExists = false; + _emitBlankLineBeforeNextStatement = false; _writer.WriteBlockStart($"namespace {ProjectName}"); EmitHelperUsingStatements(); _writer.WriteBlankLine(); - _writer.WriteLine("/// Provide core binding logic."); - _writer.WriteBlockStart($"internal static class {Identifier.CoreBindingHelper}"); + _writer.WriteBlock($$""" + /// Provide core binding logic. + {{GetGeneratedCodeAttributeSrc()}} + file static class {{Identifier.CoreBindingHelper}} + { + """); + EmitConfigurationKeyCaches(); EmitGetCoreMethod(); EmitGetValueCoreMethod(); EmitBindCoreUntypedMethod(); @@ -49,6 +54,32 @@ private void EmitHelperUsingStatements() } } + private void EmitConfigurationKeyCaches() + { + if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.BindCore, out HashSet targetTypes)) + { + return; + } + + foreach (TypeSpec type in targetTypes) + { + if (type is not ObjectSpec objectType) + { + continue; + } + + HashSet keys = new(objectType.ConstructorParameters.Select(m => GetCacheElement(m))); + keys.UnionWith(objectType.Properties.Values.Select(m => GetCacheElement(m))); + static string GetCacheElement(MemberSpec member) => $@"""{member.ConfigurationKeyName}"""; + + string configKeysSource = string.Join(", ", keys); + string fieldName = GetConfigKeyCacheFieldName(objectType); + _writer.WriteLine($@"private readonly static Lazy<{MinimalDisplayString.HashSetOfString}> {fieldName} = new(() => new {MinimalDisplayString.HashSetOfString}(StringComparer.OrdinalIgnoreCase) {{ {configKeysSource} }});"); + } + + _emitBlankLineBeforeNextStatement = true; + } + private void EmitGetCoreMethod() { if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.GetCore, out HashSet? types)) @@ -56,7 +87,8 @@ private void EmitGetCoreMethod() return; } - _writer.WriteBlockStart($"public static object? {Identifier.GetCore}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, Action<{Identifier.BinderOptions}>? {Identifier.configureOptions})"); + EmitBlankLineIfRequired(); + _writer.WriteBlockStart($"public static object? {nameof(MethodsToGen_CoreBindingHelper.GetCore)}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, Action<{Identifier.BinderOptions}>? {Identifier.configureOptions})"); EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); @@ -67,11 +99,24 @@ private void EmitGetCoreMethod() foreach (TypeSpec type in types) { + TypeSpecKind kind = type.SpecKind; + _writer.WriteBlockStart($"if (type == typeof({type.MinimalDisplayString}))"); - if (type.InitializationStrategy is InitializationStrategy.None || !EmitInitException(type)) + if (type is ParsableFromStringSpec stringParsableType) { - EmitBindLogicFromRootMethod(type, Identifier.obj, InitializationKind.Declaration); + EmitCastToIConfigurationSection(); + EmitBindLogicFromString( + stringParsableType, + Expression.sectionValue, + Expression.sectionPath, + writeOnSuccess: parsedValueExpr => _writer.WriteLine($"return {parsedValueExpr};"), + checkForNullSectionValue: stringParsableType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue, + useIncrementalStringValueIdentifier: false); + } + else if (!EmitInitException(type)) + { + EmitBindCoreCall(type, Identifier.obj, Identifier.configuration, InitializationKind.Declaration); _writer.WriteLine($"return {Identifier.obj};"); } @@ -81,7 +126,7 @@ private void EmitGetCoreMethod() Emit_NotSupportedException_TypeNotDetectedAsInput(); _writer.WriteBlockEnd(); - _precedingBlockExists = true; + _emitBlankLineBeforeNextStatement = true; } private void EmitGetValueCoreMethod() @@ -92,25 +137,32 @@ private void EmitGetValueCoreMethod() } EmitBlankLineIfRequired(); - - _writer.WriteBlockStart($"public static object? {Identifier.GetValueCore}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key})"); + _writer.WriteBlockStart($"public static object? {nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key})"); EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); + _writer.WriteLine($@"{Identifier.IConfigurationSection} {Identifier.section} = {GetSectionFromConfigurationExpression(Identifier.key, addQuotes: false)};"); + _writer.WriteBlankLine(); - _writer.WriteLine($"{Identifier.IConfigurationSection} {Identifier.section} = {Identifier.configuration}.{Identifier.GetSection}({Identifier.key});"); + _writer.WriteBlock($$""" + if ({{Expression.sectionValue}} is not string {{Identifier.value}}) + { + return null; + } + """); _writer.WriteBlankLine(); foreach (TypeSpec type in targetTypes) { - ParsableFromStringSpec effectiveType = (ParsableFromStringSpec)((type as NullableSpec)?.UnderlyingType ?? type); _writer.WriteBlockStart($"if ({Identifier.type} == typeof({type.MinimalDisplayString}))"); EmitBindLogicFromString( - effectiveType, - Expression.sectionValue, - Expression.sectionPath, - writeOnSuccess: (parsedValueExpr) => _writer.WriteLine($"return {parsedValueExpr};")); + (ParsableFromStringSpec)type.EffectiveType, + Identifier.value, + Expression.sectionPath, + writeOnSuccess: (parsedValueExpr) => _writer.WriteLine($"return {parsedValueExpr};"), + checkForNullSectionValue: false, + useIncrementalStringValueIdentifier: false); _writer.WriteBlockEnd(); _writer.WriteBlankLine(); @@ -118,7 +170,7 @@ private void EmitGetValueCoreMethod() _writer.WriteLine("return null;"); _writer.WriteBlockEnd(); - _precedingBlockExists = true; + _emitBlankLineBeforeNextStatement = true; } private void EmitBindCoreUntypedMethod() @@ -130,7 +182,7 @@ private void EmitBindCoreUntypedMethod() EmitBlankLineIfRequired(); - _writer.WriteBlockStart($"public static void {Identifier.BindCoreUntyped}(this {Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.obj}, Type {Identifier.type}, {MinimalDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})"); + _writer.WriteBlockStart($"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)}(this {Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.obj}, Type {Identifier.type}, {MinimalDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})"); EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); @@ -143,10 +195,11 @@ private void EmitBindCoreUntypedMethod() { _writer.WriteBlockStart($"if (type == typeof({type.MinimalDisplayString}))"); - if (type.InitializationStrategy is InitializationStrategy.None || !EmitInitException(type)) + TypeSpec effectiveType = type.EffectiveType; + if (!EmitInitException(effectiveType)) { - _writer.WriteLine($"var {Identifier.temp} = ({type.MinimalDisplayString}){Identifier.obj};"); - EmitBindLogicFromRootMethod(type, Identifier.temp, InitializationKind.None); + _writer.WriteLine($"var {Identifier.temp} = ({effectiveType.MinimalDisplayString}){Identifier.obj};"); + EmitBindCoreCall(type, Identifier.temp, Identifier.configuration, InitializationKind.None); _writer.WriteLine($"return;"); } @@ -156,7 +209,7 @@ private void EmitBindCoreUntypedMethod() Emit_NotSupportedException_TypeNotDetectedAsInput(); _writer.WriteBlockEnd(); - _precedingBlockExists = true; + _emitBlankLineBeforeNextStatement = true; } private void EmitBindCoreMethods() @@ -168,11 +221,7 @@ private void EmitBindCoreMethods() foreach (TypeSpec type in targetTypes) { - if (type.SpecKind is TypeSpecKind.ParsableFromString) - { - continue; - } - + Debug.Assert(type.NeedsMemberBinding); EmitBlankLineIfRequired(); EmitBindCoreMethod(type); } @@ -180,14 +229,35 @@ private void EmitBindCoreMethods() private void EmitBindCoreMethod(TypeSpec type) { - if (!type.CanInitialize) + Debug.Assert(type.CanInitialize); + + string objParameterExpression = $"ref {type.MinimalDisplayString} {Identifier.obj}"; + _writer.WriteBlockStart(@$"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCore)}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); + + EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType); + + TypeSpec effectiveType = type.EffectiveType; + if (effectiveType is EnumerableSpec enumerable) { - return; + if (effectiveType.InitializationStrategy is InitializationStrategy.Array) + { + Debug.Assert(type == effectiveType); + EmitPopulationImplForArray((EnumerableSpec)type); + } + else + { + EmitPopulationImplForEnumerableWithAdd(enumerable); + } + } + else if (effectiveType is DictionarySpec dictionary) + { + EmitBindCoreImplForDictionary(dictionary); + } + else + { + EmitBindCoreImplForObject((ObjectSpec)effectiveType); } - string objParameterExpression = $"ref {type.MinimalDisplayString} {Identifier.obj}"; - _writer.WriteBlockStart(@$"public static void {Identifier.BindCore}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); - EmitBindCoreImpl(type); _writer.WriteBlockEnd(); } @@ -208,84 +278,39 @@ private void EmitInitializeMethods() private void EmitInitializeMethod(ObjectSpec type) { Debug.Assert(type.CanInitialize); - List ctorParams = type.ConstructorParameters; - IEnumerable initOnlyProps = type.Properties.Values.Where(prop => prop.SetOnInit); + IEnumerable initOnlyProps = type.Properties.Values.Where(prop => prop is { SetOnInit: true }); + List ctorArgList = new(); string displayString = type.MinimalDisplayString; - _writer.WriteBlockStart($"public static {displayString} {type.InitializeMethodDisplayString}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); + _writer.WriteBlockStart($"public static {type.MinimalDisplayString} {GetInitalizeMethodDisplayString(type)}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); + _emitBlankLineBeforeNextStatement = false; foreach (ParameterSpec parameter in ctorParams) { - if (!parameter.HasExplicitDefaultValue) + string name = parameter.Name; + string argExpr = parameter.RefKind switch { - _writer.WriteLine($@"({parameter.Type.MinimalDisplayString} {Identifier.Value}, bool {Identifier.HasConfig}) {parameter.Name} = ({parameter.DefaultValue}, false);"); - } - else - { - _writer.WriteLine($@"{parameter.Type.MinimalDisplayString} {parameter.Name} = {parameter.DefaultValue};"); - } - } - - foreach (PropertySpec property in initOnlyProps) - { - if (property.MatchingCtorParam is null) - { - _writer.WriteLine($@"{property.Type.MinimalDisplayString} {property.Name} = default!;"); - } - } - - _writer.WriteBlankLine(); - - _writer.WriteBlock($$""" - foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}()) - { - switch ({{Expression.sectionKey}}) - { - """); - - List argumentList = new(); + RefKind.None => name, + RefKind.Ref => $"ref {name}", + RefKind.Out => "out _", + RefKind.In => $"in {name}", + _ => throw new InvalidOperationException() + }; - foreach (ParameterSpec parameter in ctorParams) - { - EmitMemberBindLogic(parameter.Name, parameter.Type, parameter.ConfigurationKeyName, configValueMustExist: !parameter.HasExplicitDefaultValue); - argumentList.Add(GetExpressionForArgument(parameter)); + ctorArgList.Add(argExpr); + EmitBindImplForMember(parameter); } foreach (PropertySpec property in initOnlyProps) { if (property.ShouldBind() && property.MatchingCtorParam is null) { - EmitMemberBindLogic(property.Name, property.Type, property.ConfigurationKeyName); + EmitBindImplForMember(property); } } - EmitSwitchDefault("continue;", addBreak: false); - - _writer.WriteBlockEnd(); - _writer.WriteBlockEnd(); - - _precedingBlockExists = true; - - foreach (ParameterSpec parameter in ctorParams) - { - if (!parameter.HasExplicitDefaultValue) - { - string parameterName = parameter.Name; - - EmitBlankLineIfRequired(); - _writer.WriteBlock($$""" - if (!{{parameterName}}.{{Identifier.HasConfig}}) - { - throw new {{GetInvalidOperationDisplayName()}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.Name, parameterName)}}"); - } - """); - } - } - - EmitBlankLineIfRequired(); - - string returnExpression = $"return new {displayString}({string.Join(", ", argumentList)})"; + string returnExpression = $"return new {displayString}({string.Join(", ", ctorArgList)})"; if (!initOnlyProps.Any()) { _writer.WriteLine($"{returnExpression};"); @@ -296,87 +321,104 @@ private void EmitInitializeMethod(ObjectSpec type) foreach (PropertySpec property in initOnlyProps) { string propertyName = property.Name; - string initValue = propertyName + (property.MatchingCtorParam is null or ParameterSpec { HasExplicitDefaultValue: true } ? string.Empty : $".{Identifier.Value}"); - _writer.WriteLine($@"{propertyName} = {initValue},"); + _writer.WriteLine($@"{propertyName} = {propertyName},"); } _writer.WriteBlockEnd(";"); } // End method. _writer.WriteBlockEnd(); + _emitBlankLineBeforeNextStatement = true; - void EmitMemberBindLogic(string memberName, TypeSpec memberType, string configurationKeyName, bool configValueMustExist = false) + void EmitBindImplForMember(MemberSpec member) { - string lhs = memberName + (configValueMustExist ? $".{Identifier.Value}" : string.Empty); - - _writer.WriteLine($@"case ""{configurationKeyName}"":"); - _writer.Indentation++; - _writer.WriteBlockStart(); + TypeSpec memberType = member.Type; + bool errorOnFailedBinding = member.ErrorOnFailedBinding; - EmitMemberBindLogicCore(memberType, lhs); + string parsedMemberIdentifierDeclarationPrefix = $"{memberType.MinimalDisplayString} {member.Name}"; + string parsedMemberIdentifier; - if (configValueMustExist) + if (memberType is ParsableFromStringSpec { StringParsableTypeKind: StringParsableTypeKind.AssignFromSectionValue }) { - _writer.WriteLine($"{memberName}.{Identifier.HasConfig} = true;"); - } - - _writer.WriteBlockEnd(); - _writer.WriteLine("break;"); - _writer.Indentation--; - - void EmitMemberBindLogicCore(TypeSpec type, string lhs) - { - TypeSpecKind kind = type.SpecKind; + parsedMemberIdentifier = parsedMemberIdentifierDeclarationPrefix; - if (kind is TypeSpecKind.Nullable) + if (errorOnFailedBinding) { - EmitMemberBindLogicCore(((NullableSpec)type).UnderlyingType, lhs); + string condition = $@" if ({Identifier.configuration}[""{member.ConfigurationKeyName}""] is not {memberType.MinimalDisplayString} {member.Name})"; + EmitThrowBlock(condition); + _writer.WriteBlankLine(); + return; } - else if (type is ParsableFromStringSpec stringParsableType) + } + else + { + parsedMemberIdentifier = member.Name; + + string declarationSuffix; + if (errorOnFailedBinding) { - EmitBindLogicFromString( - stringParsableType, - Expression.sectionValue, - Expression.sectionPath, - (parsedValueExpr) => _writer.WriteLine($"{lhs} = {parsedValueExpr}!;")); + declarationSuffix = ";"; } - else if (!EmitInitException(type)) + else { - EmitBindCoreCall(type, lhs, Identifier.section, InitializationKind.SimpleAssignment); + string bangExpr = memberType.IsValueType ? string.Empty : "!"; + declarationSuffix = memberType.CanInitialize + ? $" = {member.DefaultValueExpr}{bangExpr};" + : ";"; } + + string parsedMemberIdentifierDeclaration = $"{parsedMemberIdentifierDeclarationPrefix}{declarationSuffix}"; + _writer.WriteLine(parsedMemberIdentifierDeclaration); + _emitBlankLineBeforeNextStatement = false; } - } - static string GetExpressionForArgument(ParameterSpec parameter) - { - string name = parameter.Name + (parameter.HasExplicitDefaultValue ? string.Empty : $".{Identifier.Value}"); + bool canBindToMember = this.EmitBindImplForMember( + member, + parsedMemberIdentifier, + sectionPathExpr: GetSectionPathFromConfigurationExpression(member.ConfigurationKeyName), + canSet: true); - return parameter.RefKind switch + if (canBindToMember) { - RefKind.None => name, - RefKind.Ref => $"ref {name}", - RefKind.Out => "out _", - RefKind.In => $"in {name}", - _ => throw new InvalidOperationException() - }; + if (errorOnFailedBinding) + { + // Add exception logic for parameter ctors; must be present in configuration object. + EmitThrowBlock(condition: "else"); + } + + _writer.WriteBlankLine(); + } + + void EmitThrowBlock(string condition) => + _writer.WriteBlock($$""" + {{condition}} + { + throw new {{GetInvalidOperationDisplayName()}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.Name, member.Name)}}"); + } + """); } } private void EmitHelperMethods() { + if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCore)) + { + EmitValidateConfigurationKeysMethod(); + } + if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreUntyped | MethodsToGen_CoreBindingHelper.GetCore)) { _writer.WriteBlankLine(); EmitHasValueOrChildrenMethod(); _writer.WriteBlankLine(); - EmitHasChildrenMethod(); - _precedingBlockExists = true; + EmitAsConfigWithChildrenMethod(); + _emitBlankLineBeforeNextStatement = true; } - else if (_sourceGenSpec.ShouldEmitHasChildren) + else if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.AsConfigWithChildren)) { _writer.WriteBlankLine(); - EmitHasChildrenMethod(); - _precedingBlockExists = true; + EmitAsConfigWithChildrenMethod(); + _emitBlankLineBeforeNextStatement = true; } if (ShouldEmitMethods( @@ -385,7 +427,7 @@ private void EmitHelperMethods() { _writer.WriteBlankLine(); EmitGetBinderOptionsHelper(); - _precedingBlockExists = true; + _emitBlankLineBeforeNextStatement = true; } foreach (ParsableFromStringSpec type in _sourceGenSpec.PrimitivesForHelperGen) @@ -395,6 +437,37 @@ private void EmitHelperMethods() } } + private void EmitValidateConfigurationKeysMethod() + { + const string keysIdentifier = "keys"; + string exceptionMessage = string.Format(ExceptionMessages.MissingConfig, Identifier.ErrorOnUnknownConfiguration, Identifier.BinderOptions, $"{{{Identifier.type}}}", $@"{{string.Join("", "", {Identifier.temp})}}"); + + EmitBlankLineIfRequired(); + _writer.WriteBlock($$""" + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void {{Identifier.ValidateConfigurationKeys}}(Type {{Identifier.type}}, {{MinimalDisplayString.LazyHashSetOfString}} {{keysIdentifier}}, {{Identifier.IConfiguration}} {{Identifier.configuration}}, {{Identifier.BinderOptions}}? {{Identifier.binderOptions}}) + { + if ({{Identifier.binderOptions}}?.{{Identifier.ErrorOnUnknownConfiguration}} is true) + { + {{MinimalDisplayString.ListOfString}}? {{Identifier.temp}} = null; + + foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}()) + { + if (!{{keysIdentifier}}.Value.Contains({{Expression.sectionKey}})) + { + ({{Identifier.temp}} ??= new {{MinimalDisplayString.ListOfString}}()).Add($"'{{{Expression.sectionKey}}}'"); + } + } + + if ({{Identifier.temp}} is not null) + { + throw new InvalidOperationException($"{{exceptionMessage}}"); + } + } + } + """); + } + private void EmitHasValueOrChildrenMethod() { _writer.WriteBlock($$""" @@ -404,21 +477,21 @@ private void EmitHasValueOrChildrenMethod() { return true; } - return {{Identifier.HasChildren}}({{Identifier.configuration}}); + return {{Identifier.AsConfigWithChildren}}({{Identifier.configuration}}) is not null; } """); } - private void EmitHasChildrenMethod() + private void EmitAsConfigWithChildrenMethod() { _writer.WriteBlock($$""" - public static bool {{Identifier.HasChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}}) + public static {{Identifier.IConfiguration}}? {{Identifier.AsConfigWithChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}}) { - foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}()) + foreach ({{Identifier.IConfigurationSection}} _ in {{Identifier.configuration}}.{{Identifier.GetChildren}}()) { - return true; + return {{Identifier.configuration}}; } - return false; + return null; } """); } @@ -465,52 +538,52 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) numberStylesTypeDisplayString = "NumberStyles"; } - string invariantCultureExpression = $"{cultureInfoTypeDisplayString}.InvariantCulture"; - - string expressionForParsedValue; StringParsableTypeKind typeKind = type.StringParsableTypeKind; string typeDisplayString = type.MinimalDisplayString; + string invariantCultureExpression = $"{cultureInfoTypeDisplayString}.InvariantCulture"; + + string parsedValueExpr; switch (typeKind) { case StringParsableTypeKind.Enum: { - expressionForParsedValue = $"({typeDisplayString}){Identifier.Enum}.{Identifier.Parse}(typeof({typeDisplayString}), {Identifier.stringValue}, ignoreCase: true)"; + parsedValueExpr = $"({typeDisplayString}){Identifier.Enum}.{Identifier.Parse}(typeof({typeDisplayString}), {Identifier.value}, ignoreCase: true)"; } break; case StringParsableTypeKind.ByteArray: { - expressionForParsedValue = $"Convert.FromBase64String({Identifier.stringValue})"; + parsedValueExpr = $"Convert.FromBase64String({Identifier.value})"; } break; case StringParsableTypeKind.Integer: { - expressionForParsedValue = $"{typeDisplayString}.{Identifier.Parse}({Identifier.stringValue}, {numberStylesTypeDisplayString}.Integer, {invariantCultureExpression})"; + parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {numberStylesTypeDisplayString}.Integer, {invariantCultureExpression})"; } break; case StringParsableTypeKind.Float: { - expressionForParsedValue = $"{typeDisplayString}.{Identifier.Parse}({Identifier.stringValue}, {numberStylesTypeDisplayString}.Float, {invariantCultureExpression})"; + parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {numberStylesTypeDisplayString}.Float, {invariantCultureExpression})"; } break; case StringParsableTypeKind.Parse: { - expressionForParsedValue = $"{typeDisplayString}.{Identifier.Parse}({Identifier.stringValue})"; + parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value})"; } break; case StringParsableTypeKind.ParseInvariant: { - expressionForParsedValue = $"{typeDisplayString}.{Identifier.Parse}({Identifier.stringValue}, {invariantCultureExpression})"; ; + parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {invariantCultureExpression})"; ; } break; case StringParsableTypeKind.CultureInfo: { - expressionForParsedValue = $"{cultureInfoTypeDisplayString}.GetCultureInfo({Identifier.stringValue})"; + parsedValueExpr = $"{cultureInfoTypeDisplayString}.GetCultureInfo({Identifier.value})"; } break; case StringParsableTypeKind.Uri: { - expressionForParsedValue = $"new Uri({Identifier.stringValue}, UriKind.RelativeOrAbsolute)"; + parsedValueExpr = $"new Uri({Identifier.value}, UriKind.RelativeOrAbsolute)"; } break; default: @@ -521,11 +594,11 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) } _writer.WriteBlock($$""" - public static {{typeDisplayString}} {{type.ParseMethodName}}(string {{Identifier.stringValue}}, Func {{Identifier.getPath}}) + public static {{typeDisplayString}} {{type.ParseMethodName}}(string {{Identifier.value}}, Func {{Identifier.getPath}}) { try { - return {{expressionForParsedValue}}; + return {{parsedValueExpr}}; """); string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof({typeDisplayString})}}"); @@ -540,69 +613,19 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) """); } - private void EmitBindCoreImpl(TypeSpec type) - { - switch (type.SpecKind) - { - case TypeSpecKind.Enumerable: - case TypeSpecKind.Dictionary: - case TypeSpecKind.Object: - { - Debug.Assert(type.CanInitialize); - EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType); - EmitBindCoreImplForComplexType(type); - } - break; - case TypeSpecKind.Nullable: - { - EmitBindCoreImpl(((NullableSpec)type).UnderlyingType); - } - break; - case TypeSpecKind.IConfigurationSection: - { - EmitCastToIConfigurationSection(); - _writer.WriteLine($"{Identifier.obj} = {Identifier.section};"); - } - break; - default: - Debug.Fail("Invalid type kind", type.SpecKind.ToString()); - break; - } - } - - private void EmitBindCoreImplForComplexType(TypeSpec type) - { - if (type.InitializationStrategy is InitializationStrategy.Array) - { - EmitPopulationImplForArray((EnumerableSpec)type); - } - else if (type is EnumerableSpec enumerable) - { - EmitPopulationImplForEnumerableWithAdd(enumerable); - } - else if (type is DictionarySpec dictionary) - { - EmitBindCoreImplForDictionary(dictionary); - } - else - { - EmitBindCoreImplForObject((ObjectSpec)type); - } - } - private void EmitPopulationImplForArray(EnumerableSpec type) { EnumerableSpec concreteType = (EnumerableSpec)type.ConcreteType; - // Create, bind, and add elements to temp list. - string tempVarName = GetIncrementalVarName(Identifier.temp); - EmitBindCoreCall(concreteType, tempVarName, Identifier.configuration, InitializationKind.Declaration); + // Create list and bind elements. + string tempIdentifier = GetIncrementalIdentifier(Identifier.temp); + EmitBindCoreCall(concreteType, tempIdentifier, Identifier.configuration, InitializationKind.Declaration); - // Resize array and copy additional elements. + // Resize array and add binded elements. _writer.WriteBlock($$""" {{Identifier.Int32}} {{Identifier.originalCount}} = {{Identifier.obj}}.{{Identifier.Length}}; - {{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.obj}}, {{Identifier.originalCount}} + {{tempVarName}}.{{Identifier.Count}}); - {{tempVarName}}.{{Identifier.CopyTo}}({{Identifier.obj}}, {{Identifier.originalCount}}); + {{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.obj}}, {{Identifier.originalCount}} + {{tempIdentifier}}.{{Identifier.Count}}); + {{tempIdentifier}}.{{Identifier.CopyTo}}({{Identifier.obj}}, {{Identifier.originalCount}}); """); } @@ -610,7 +633,7 @@ private void EmitPopulationImplForEnumerableWithAdd(EnumerableSpec type) { EmitCollectionCastIfRequired(type, out string objIdentifier); - _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())"); + Emit_Foreach_Section_In_ConfigChildren_BlockHeader(); TypeSpec elementType = type.ElementType; @@ -620,13 +643,14 @@ private void EmitPopulationImplForEnumerableWithAdd(EnumerableSpec type) stringParsableType, Expression.sectionValue, Expression.sectionPath, - (parsedValueExpr) => _writer.WriteLine($"{objIdentifier}.{Identifier.Add}({parsedValueExpr}!);"), - isCollectionElement: true); + (parsedValueExpr) => _writer.WriteLine($"{objIdentifier}.{Identifier.Add}({parsedValueExpr});"), + checkForNullSectionValue: true, + useIncrementalStringValueIdentifier: false); } else { - EmitBindCoreCall(elementType, Identifier.element, Identifier.section, InitializationKind.Declaration); - _writer.WriteLine($"{objIdentifier}.{Identifier.Add}({Identifier.element});"); + EmitBindCoreCall(elementType, Identifier.value, Identifier.section, InitializationKind.Declaration); + _writer.WriteLine($"{objIdentifier}.{Identifier.Add}({Identifier.value});"); } _writer.WriteBlockEnd(); @@ -636,17 +660,19 @@ private void EmitBindCoreImplForDictionary(DictionarySpec type) { EmitCollectionCastIfRequired(type, out string objIdentifier); - _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())"); + Emit_Foreach_Section_In_ConfigChildren_BlockHeader(); ParsableFromStringSpec keyType = type.KeyType; TypeSpec elementType = type.ElementType; // Parse key EmitBindLogicFromString( - keyType, - Expression.sectionKey, - Expression.sectionPath, - Emit_BindAndAddLogic_ForElement); + keyType, + Expression.sectionKey, + Expression.sectionPath, + Emit_BindAndAddLogic_ForElement, + checkForNullSectionValue: false, + useIncrementalStringValueIdentifier: false); void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) { @@ -656,15 +682,15 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) stringParsableElementType, Expression.sectionValue, Expression.sectionPath, - (parsedValueExpr) => _writer.WriteLine($"{objIdentifier}[{parsedKeyExpr}!] = {parsedValueExpr}!;"), - isCollectionElement: true); + writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{objIdentifier}[{parsedKeyExpr}] = {parsedValueExpr};"), + checkForNullSectionValue: true, + useIncrementalStringValueIdentifier: false); } else // For complex types: { Debug.Assert(elementType.CanInitialize); - parsedKeyExpr += "!"; - if (keyType.StringParsableTypeKind is not StringParsableTypeKind.ConfigValue) + if (keyType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) { // Save value to local to avoid parsing twice - during look-up and during add. _writer.WriteLine($"{keyType.MinimalDisplayString} {Identifier.key} = {parsedKeyExpr};"); @@ -685,7 +711,7 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) } _writer.WriteBlockStart($"if (!({conditionToUseExistingElement}))"); - EmitObjectInit(elementType, Identifier.element, InitializationKind.SimpleAssignment); + EmitObjectInit(elementType, Identifier.element, InitializationKind.SimpleAssignment, Identifier.section); _writer.WriteBlockEnd(); if (elementType is CollectionSpec { InitializationStrategy: InitializationStrategy.ParameterizedConstructor or InitializationStrategy.ToEnumerableMethod } collectionSpec) @@ -716,175 +742,152 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) private void EmitBindCoreImplForObject(ObjectSpec type) { - if (type.Properties.Count == 0) - { - return; - } + Debug.Assert(type.NeedsMemberBinding); - string listOfStringDisplayName = "List"; - _writer.WriteLine($"{listOfStringDisplayName}? {Identifier.temp} = null;"); - - _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())"); - _writer.WriteBlockStart($"switch ({Expression.sectionKey})"); + string keyCacheFieldName = GetConfigKeyCacheFieldName(type); + string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.MinimalDisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});"; + _writer.WriteLine(validateMethodCallExpr); foreach (PropertySpec property in type.Properties.Values) { - _writer.WriteLine($@"case ""{property.ConfigurationKeyName}"":"); - _writer.Indentation++; - _writer.WriteBlockStart(); - - bool success = true; - if (property.ShouldBind()) + bool noSetter_And_IsReadonly = !property.CanSet && property.Type is CollectionSpec { InitializationStrategy: InitializationStrategy.ParameterizedConstructor }; + if (property.ShouldBind() && !noSetter_And_IsReadonly) { - success = EmitBindCoreImplForProperty(property, property.Type, parentType: type); + string containingTypeRef = property.IsStatic ? type.MinimalDisplayString : Identifier.obj; + EmitBindImplForMember( + property, + memberAccessExpr: $"{containingTypeRef}.{property.Name}", + GetSectionPathFromConfigurationExpression(property.ConfigurationKeyName), + canSet: property.CanSet); } + } + } - _writer.WriteBlockEnd(); + private bool EmitBindImplForMember( + MemberSpec member, + string memberAccessExpr, + string sectionPathExpr, + bool canSet) + { + TypeSpec effectiveMemberType = member.Type.EffectiveType; - if (success) + if (effectiveMemberType is ParsableFromStringSpec stringParsableType) + { + if (canSet) { - _writer.WriteLine("break;"); - } + bool checkForNullSectionValue = member is ParameterSpec + ? true + : stringParsableType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue; - _writer.Indentation--; - } - - EmitSwitchDefault($$""" - if ({{Identifier.binderOptions}}?.ErrorOnUnknownConfiguration == true) - { - ({{Identifier.temp}} ??= new {{listOfStringDisplayName}}()).Add($"'{{{Expression.sectionKey}}}'"); - } - """); + string nullBangExpr = checkForNullSectionValue ? string.Empty : "!"; - // End switch on config child key. - _writer.WriteBlockEnd(); + EmitBlankLineIfRequired(); + EmitBindLogicFromString( + stringParsableType, + $@"{Identifier.configuration}[""{member.ConfigurationKeyName}""]", + sectionPathExpr, + writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{memberAccessExpr} = {parsedValueExpr}{nullBangExpr};"), + checkForNullSectionValue, + useIncrementalStringValueIdentifier: true); + } - // End foreach on config.GetChildren(). - _writer.WriteBlockEnd(); + return true; + } - _writer.WriteBlankLine(); + string sectionParseExpr = $"{GetSectionFromConfigurationExpression(member.ConfigurationKeyName)}"; - string exceptionMessage = string.Format(ExceptionMessages.MissingConfig, Identifier.ErrorOnUnknownConfiguration, Identifier.BinderOptions, $"{{typeof({type.MinimalDisplayString})}}", $@"{{string.Join("", "", {Identifier.temp})}}"); - _writer.WriteBlock($$""" - if ({{Identifier.temp}} is not null) - { - throw new InvalidOperationException($"{{exceptionMessage}}"); - } - """); + EmitBlankLineIfRequired(); - } + if (effectiveMemberType.SpecKind is TypeSpecKind.IConfigurationSection) + { + _writer.WriteLine($"{memberAccessExpr} = {sectionParseExpr};"); + return true; + } - private bool EmitBindCoreImplForProperty(PropertySpec property, TypeSpec propertyType, TypeSpec parentType) - { - string configurationKeyName = property.ConfigurationKeyName; - string propertyParentReference = property.IsStatic ? parentType.MinimalDisplayString : Identifier.obj; - string expressionForPropertyAccess = $"{propertyParentReference}.{property.Name}"; - string expressionForConfigValueIndexer = $@"{Identifier.configuration}[""{configurationKeyName}""]"; + string sectionValidationCall = $"{Identifier.AsConfigWithChildren}({sectionParseExpr})"; + string sectionIdentifier = GetIncrementalIdentifier(Identifier.section); - bool canSet = property.CanSet; + _writer.WriteBlockStart($"if ({sectionValidationCall} is {Identifier.IConfigurationSection} {sectionIdentifier})"); - switch (propertyType.SpecKind) + bool success = !EmitInitException(effectiveMemberType); + if (success) { - case TypeSpecKind.ParsableFromString: - { - if (canSet && propertyType is ParsableFromStringSpec stringParsableType) - { - EmitBindLogicFromString( - stringParsableType, - expressionForConfigValueIndexer, - Expression.sectionPath, - (parsedValueExpr) => _writer.WriteLine($"{expressionForPropertyAccess} = {parsedValueExpr}!;")); - } - } - break; - case TypeSpecKind.IConfigurationSection: - { - _writer.WriteLine($"{expressionForPropertyAccess} = {Identifier.section};"); - } - break; - case TypeSpecKind.Nullable: - { - TypeSpec underlyingType = ((NullableSpec)propertyType).UnderlyingType; - EmitBindCoreImplForProperty(property, underlyingType, parentType); - } - break; - default: - { - if (EmitInitException(propertyType)) - { - return false; - } - - EmitBindCoreCallForProperty(property, propertyType, expressionForPropertyAccess); - } - break; + EmitBindCoreCallForMember(member, memberAccessExpr, sectionIdentifier, canSet); } - return true; + _writer.WriteBlockEnd(); + return success; } - private void EmitBindCoreCallForProperty(PropertySpec property, TypeSpec effectivePropertyType, string expressionForPropertyAccess) + private void EmitBindCoreCallForMember( + MemberSpec member, + string memberAccessExpr, + string configArgExpr, + bool canSet) { - _writer.WriteBlockStart($"if ({Identifier.HasChildren}({Identifier.section}))"); - bool canGet = property.CanGet; - bool canSet = property.CanSet; - string effectivePropertyTypeDisplayString = effectivePropertyType.MinimalDisplayString; + TypeSpec memberType = member.Type; + TypeSpec effectiveMemberType = memberType.EffectiveType; + string effectiveMemberTypeDisplayString = effectiveMemberType.MinimalDisplayString; + bool canGet = member.CanGet; - string tempVarName = GetIncrementalVarName(Identifier.temp); - if (effectivePropertyType.IsValueType) + string tempIdentifier = GetIncrementalIdentifier(Identifier.temp); + InitializationKind initKind; + string targetObjAccessExpr; + + if (effectiveMemberType.IsValueType) { - if (canSet) + if (!canSet) { - if (canGet) - { - TypeSpec actualPropertyType = property.Type; - if (actualPropertyType.SpecKind is TypeSpecKind.Nullable) - { - string nullableTempVarName = GetIncrementalVarName(Identifier.temp); + return; + } - _writer.WriteLine($"{actualPropertyType.MinimalDisplayString} {nullableTempVarName} = {expressionForPropertyAccess};"); + Debug.Assert(canSet); + initKind = InitializationKind.None; - _writer.WriteLine( - $"{effectivePropertyTypeDisplayString} {tempVarName} = {nullableTempVarName}.{Identifier.HasValue} ? {nullableTempVarName}.{Identifier.Value} : new {effectivePropertyTypeDisplayString}();"); - } - else - { - _writer.WriteLine($"{effectivePropertyTypeDisplayString} {tempVarName} = {expressionForPropertyAccess};"); - } - } - else - { - EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.Declaration); - } - - _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});"); - _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};"); - } - } - else - { - if (canGet) + if (memberType.SpecKind is TypeSpecKind.Nullable) { - _writer.WriteLine($"{effectivePropertyTypeDisplayString} {tempVarName} = {expressionForPropertyAccess};"); - EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.AssignmentWithNullCheck); - _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});"); + string nullableTempIdentifier = GetIncrementalIdentifier(Identifier.temp); - if (canSet) - { - _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};"); - } + _writer.WriteLine($"{memberType.MinimalDisplayString} {nullableTempIdentifier} = {memberAccessExpr};"); + + _writer.WriteLine( + $"{effectiveMemberTypeDisplayString} {tempIdentifier} = {nullableTempIdentifier}.{Identifier.HasValue} ? {nullableTempIdentifier}.{Identifier.Value} : new {effectiveMemberTypeDisplayString}();"); } else { - Debug.Assert(canSet); - EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.Declaration); - _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});"); - _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};"); + _writer.WriteLine($"{effectiveMemberTypeDisplayString} {tempIdentifier} = {memberAccessExpr};"); } - } - _writer.WriteBlockEnd(); + targetObjAccessExpr = tempIdentifier; + } + else if (canGet) + { + targetObjAccessExpr = memberAccessExpr; + initKind = InitializationKind.AssignmentWithNullCheck; + } + else + { + targetObjAccessExpr = memberAccessExpr; + initKind = InitializationKind.SimpleAssignment; + } + + Action? writeOnSuccess = !canSet + ? null + : bindedValueIdentifier => + { + if (memberAccessExpr != bindedValueIdentifier) + { + _writer.WriteLine($"{memberAccessExpr} = {bindedValueIdentifier};"); + } + }; + + EmitBindCoreCall( + effectiveMemberType, + targetObjAccessExpr, + configArgExpr, + initKind, + writeOnSuccess); } private void EmitCollectionCastIfRequired(CollectionSpec type, out string objIdentifier) @@ -903,22 +906,21 @@ private void EmitCollectionCastIfRequired(CollectionSpec type, out string objIde } } - private void EmitSwitchDefault(string caseLogic, bool addBreak = true) - { - _writer.WriteLine("default:"); - _writer.Indentation++; - _writer.WriteBlockStart(); - _writer.WriteBlock(caseLogic); - _writer.WriteBlockEnd(); + private void Emit_Foreach_Section_In_ConfigChildren_BlockHeader() => + _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())"); - if (addBreak) - { - _writer.WriteLine("break;"); - } + private static string GetSectionPathFromConfigurationExpression(string configurationKeyName) + => $@"{GetSectionFromConfigurationExpression(configurationKeyName)}.{Identifier.Path}"; - _writer.Indentation--; + private static string GetSectionFromConfigurationExpression(string configurationKeyName, bool addQuotes = true) + { + string argExpr = addQuotes ? $@"""{configurationKeyName}""" : configurationKeyName; + return $@"{Expression.configurationGetSection}({argExpr})"; } + private static string GetConfigKeyCacheFieldName(ObjectSpec type) => + $"s_configKeys_{type.DisplayStringWithoutSpecialCharacters}"; + private void Emit_NotSupportedException_TypeNotDetectedAsInput() => _writer.WriteLine(@$"throw new global::System.NotSupportedException($""{string.Format(ExceptionMessages.TypeNotDetectedAsInput, "{type}")}"");"); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs index dd4ba3907e3372..7325c292a0036a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Reflection; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -9,6 +10,8 @@ public sealed partial class ConfigurationBindingGenerator { private sealed partial class Emitter { + private static readonly AssemblyName s_assemblyName = typeof(Emitter).Assembly.GetName(); + private enum InitializationKind { None = 0, @@ -18,6 +21,7 @@ private enum InitializationKind } private static class Expression { + public const string configurationGetSection = "configuration.GetSection"; public const string sectionKey = "section.Key"; public const string sectionPath = "section.Path"; public const string sectionValue = "section.Value"; @@ -30,7 +34,7 @@ private static class FullyQualifiedDisplayString public const string ActionOfBinderOptions = $"global::System.Action"; public const string AddSingleton = $"{ServiceCollectionServiceExtensions}.AddSingleton"; public const string ConfigurationChangeTokenSource = "global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource"; - public const string CoreBindingHelper = $"global::{ConfigurationBindingGenerator.ProjectName}.{Identifier.CoreBindingHelper}"; + public const string CoreBindingHelper = $"global::{ProjectName}.{Identifier.CoreBindingHelper}"; public const string IConfiguration = "global::Microsoft.Extensions.Configuration.IConfiguration"; public const string IConfigurationSection = IConfiguration + "Section"; public const string IOptionsChangeTokenSource = "global::Microsoft.Extensions.Options.IOptionsChangeTokenSource"; @@ -45,6 +49,9 @@ private static class FullyQualifiedDisplayString private static class MinimalDisplayString { public const string NullableActionOfBinderOptions = "Action?"; + public const string HashSetOfString = "HashSet"; + public const string LazyHashSetOfString = "Lazy>"; + public const string ListOfString = "List"; } private static class Identifier @@ -64,18 +71,19 @@ private static class Identifier public const string optionsBuilder = nameof(optionsBuilder); public const string originalCount = nameof(originalCount); public const string section = nameof(section); + public const string sectionKey = nameof(sectionKey); public const string services = nameof(services); - public const string stringValue = nameof(stringValue); public const string temp = nameof(temp); public const string type = nameof(type); + public const string validateKeys = nameof(validateKeys); + public const string value = nameof(value); public const string Add = nameof(Add); public const string AddSingleton = nameof(AddSingleton); public const string Any = nameof(Any); public const string Array = nameof(Array); + public const string AsConfigWithChildren = nameof(AsConfigWithChildren); public const string Bind = nameof(Bind); - public const string BindCore = nameof(BindCore); - public const string BindCoreUntyped = nameof(BindCoreUntyped); public const string BinderOptions = nameof(BinderOptions); public const string Configure = nameof(Configure); public const string CopyTo = nameof(CopyTo); @@ -91,12 +99,9 @@ private static class Identifier public const string GeneratedServiceCollectionBinder = nameof(GeneratedServiceCollectionBinder); public const string Get = nameof(Get); public const string GetBinderOptions = nameof(GetBinderOptions); - public const string GetCore = nameof(GetCore); public const string GetChildren = nameof(GetChildren); public const string GetSection = nameof(GetSection); public const string GetValue = nameof(GetValue); - public const string GetValueCore = nameof(GetValueCore); - public const string HasChildren = nameof(HasChildren); public const string HasConfig = nameof(HasConfig); public const string HasValueOrChildren = nameof(HasValueOrChildren); public const string HasValue = nameof(HasValue); @@ -115,6 +120,7 @@ private static class Identifier public const string TryGetValue = nameof(TryGetValue); public const string TryParse = nameof(TryParse); public const string Uri = nameof(Uri); + public const string ValidateConfigurationKeys = nameof(ValidateConfigurationKeys); public const string Value = nameof(Value); } @@ -125,12 +131,12 @@ private bool ShouldEmitBinders() => private void EmitBlankLineIfRequired() { - if (_precedingBlockExists) + if (_emitBlankLineBeforeNextStatement) { _writer.WriteBlankLine(); } - _precedingBlockExists = true; + _emitBlankLineBeforeNextStatement = true; } private void EmitCheckForNullArgument_WithBlankLine_IfRequired(bool isValueType) @@ -170,9 +176,31 @@ private bool EmitInitException(TypeSpec type) return false; } + private void EmitRootBindingClassBlockStart(string className) + { + EmitBlankLineIfRequired(); + _writer.WriteBlock($$""" + /// Generated helper providing an AOT and linking compatible implementation for configuration binding. + {{GetGeneratedCodeAttributeSrc()}} + internal static class {{className}} + { + """); + + _emitBlankLineBeforeNextStatement = false; + } + + private string GetGeneratedCodeAttributeSrc() + { + string attributeRefExpr = _useFullyQualifiedNames ? $"global::System.CodeDom.Compiler.GeneratedCodeAttribute" : "GeneratedCode"; + return $@"[{attributeRefExpr}(""{s_assemblyName.Name}"", ""{s_assemblyName.Version}"")]"; + } + private string GetInitException(string message) => $@"throw new {GetInvalidOperationDisplayName()}(""{message}"")"; - private string GetIncrementalVarName(string prefix) => $"{prefix}{_parseValueCount++}"; + private string GetIncrementalIdentifier(string prefix) => $"{prefix}{_valueSuffixIndex++}"; + + private string GetInitalizeMethodDisplayString(ObjectSpec type) => + GetHelperMethodDisplayString($"{nameof(MethodsToGen_CoreBindingHelper.Initialize)}{type.DisplayStringWithoutSpecialCharacters}"); private string GetTypeDisplayString(TypeSpec type) => _useFullyQualifiedNames ? type.FullyQualifiedDisplayString : type.MinimalDisplayString; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsBuilderConfigurationExtensions.cs index e39e65e6125543..0055b2bf127c02 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsBuilderConfigurationExtensions.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.CodeAnalysis; - namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator @@ -18,10 +16,7 @@ private void EmitBinder_Extensions_OptionsBuilder() return; } - EmitBlankLineIfRequired(); - _writer.WriteLine("/// Generated helper providing an AOT and linking compatible implementation for configuration binding."); - _writer.WriteBlockStart($"internal static class {Identifier.GeneratedOptionsBuilderBinder}"); - _precedingBlockExists = false; + EmitRootBindingClassBlockStart(Identifier.GeneratedOptionsBuilderBinder); EmitBindMethods_Extensions_OptionsBuilder(); EmitBindConfigurationMethod(); @@ -44,7 +39,6 @@ private void EmitBindMethods_Extensions_OptionsBuilder() EmitMethodBlockStart("Bind", paramList, documentation); _writer.WriteLine($"return global::{Identifier.GeneratedOptionsBuilderBinder}.Bind({Identifier.optionsBuilder}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); _writer.WriteBlockEnd(); - _writer.WriteBlankLine(); } EmitMethodBlockStart( @@ -81,7 +75,7 @@ private void EmitBindConfigurationMethod() _writer.WriteBlock($$""" {{FullyQualifiedDisplayString.IConfiguration}} {{Identifier.section}} = string.Equals(string.Empty, {{Identifier.configSectionPath}}, global::System.StringComparison.OrdinalIgnoreCase) ? {{Identifier.configuration}} : {{Identifier.configuration}}.{{Identifier.GetSection}}({{Identifier.configSectionPath}}); - {{FullyQualifiedDisplayString.CoreBindingHelper}}.{{Identifier.BindCoreUntyped}}({{Identifier.section}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}}); + {{FullyQualifiedDisplayString.CoreBindingHelper}}.{{nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)}}({{Identifier.section}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}}); """); _writer.WriteBlockEnd(");"); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsConfigurationServiceCollectionExtensions.cs index 8dd6a00b786968..5b4edc93b8a51a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsConfigurationServiceCollectionExtensions.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.CodeAnalysis; - namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator @@ -18,11 +16,7 @@ private void EmitBinder_Extensions_ServiceCollection() return; } - EmitBlankLineIfRequired(); - - _writer.WriteLine("/// Generated helper providing an AOT and linking compatible implementation for configuration binding."); - _writer.WriteBlockStart($"internal static class {Identifier.GeneratedServiceCollectionBinder}"); - _precedingBlockExists = false; + EmitRootBindingClassBlockStart(Identifier.GeneratedServiceCollectionBinder); const string defaultNameExpr = "string.Empty"; const string configureMethodString = $"global::{Identifier.GeneratedServiceCollectionBinder}.{Identifier.Configure}"; @@ -52,7 +46,7 @@ private void EmitBinder_Extensions_ServiceCollection() } string optionsNamespaceName = "global::Microsoft.Extensions.Options"; - string bindCoreUntypedDisplayString = GetHelperMethodDisplayString(Identifier.BindCoreUntyped); + string bindCoreUntypedDisplayString = GetHelperMethodDisplayString(nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)); EmitBlockStart(paramList: $"string? {Identifier.name}, " + configParam + $", {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}"); @@ -67,7 +61,7 @@ private void EmitBinder_Extensions_ServiceCollection() """); _writer.WriteBlockEnd(); - _precedingBlockExists = true; + _emitBlankLineBeforeNextStatement = true; } private void EmitBlockStart(string paramList) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/MethodsToGen.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/MethodsToGen.cs index 8f2c37af6c70a5..f49deca75971cd 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/MethodsToGen.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/MethodsToGen.cs @@ -14,6 +14,7 @@ public enum MethodsToGen_CoreBindingHelper GetCore = 0x4, GetValueCore = 0x8, Initialize = 0x10, + AsConfigWithChildren = 0x20, } /// diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/ConfigurationBinder.cs index 7db7c965b153e2..26287798463ea9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/ConfigurationBinder.cs @@ -111,7 +111,6 @@ private void RegisterBindInvocation(BinderInvocation invocation) _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; typeSpecs.Add(typeSpec); - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, typeSpec); } static ITypeSymbol? ResolveType(IOperation conversionOperation) => @@ -124,6 +123,7 @@ private void RegisterBindInvocation(BinderInvocation invocation) IMethodReferenceOperation m when m.Method.MethodKind == MethodKind.Constructor => m.Method.ContainingType, IMethodReferenceOperation m => m.Method.ReturnType, IAnonymousFunctionOperation f => f.Symbol.ReturnType, + IParameterReferenceOperation p => p.Parameter.Type, _ => null }; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/SourceWriter.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/SourceWriter.cs index 4787c6999b9665..f610209874c7d0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/SourceWriter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/SourceWriter.cs @@ -14,22 +14,6 @@ internal sealed class SourceWriter private static readonly char[] s_newLine = Environment.NewLine.ToCharArray(); private int _indentation; - - public int Indentation - { - get => _indentation; - set - { - if (value < 0) - { - Throw(); - static void Throw() => throw new ArgumentOutOfRangeException(nameof(value)); - } - - _indentation = value; - } - } - public void WriteBlockStart(string? declaration = null) { if (declaration is not null) @@ -37,19 +21,19 @@ public void WriteBlockStart(string? declaration = null) WriteLine(declaration); } WriteLine("{"); - Indentation++; + _indentation++; } public void WriteBlockEnd(string? extra = null) { - Indentation--; - Debug.Assert(Indentation > -1); + _indentation--; + Debug.Assert(_indentation > -1); WriteLine($"}}{extra}"); } public void WriteLine(string source) { - _sb.Append(' ', 4 * Indentation); + _sb.Append(' ', 4 * _indentation); _sb.AppendLine(source); } @@ -86,7 +70,7 @@ public void WriteBlock(string source) public SourceText ToSourceText() { - Debug.Assert(Indentation == 0 && _sb.Length > 0); + Debug.Assert(_indentation == 0 && _sb.Length > 0); return SourceText.From(_sb.ToString(), Encoding.UTF8); } @@ -123,7 +107,7 @@ private static ReadOnlySpan GetNextLine(ref ReadOnlySpan remainingTe private unsafe void WriteLine(ReadOnlySpan source) { - _sb.Append(' ', 4 * Indentation); + _sb.Append(' ', 4 * _indentation); fixed (char* ptr = source) { _sb.Append(ptr, source.Length); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj index fc2d2bae89b2e9..f1c71a5575c23e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj @@ -41,6 +41,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/CollectionSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/CollectionSpec.cs index 7c90701fbda035..280ecc4c536482 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/CollectionSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/CollectionSpec.cs @@ -17,11 +17,13 @@ public CollectionSpec(ITypeSymbol type) : base(type) { } public required CollectionPopulationStrategy PopulationStrategy { get; init; } - public override bool CanInitialize => ConcreteType?.CanInitialize ?? CanInitCompexType; + public override bool CanInitialize => ConcreteType?.CanInitialize ?? CanInitComplexObject(); public override required InitializationStrategy InitializationStrategy { get; set; } public required string? ToEnumerableMethodCall { get; init; } + + public sealed override bool NeedsMemberBinding => true; } internal sealed record EnumerableSpec : CollectionSpec diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/MemberSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/MemberSpec.cs new file mode 100644 index 00000000000000..4bf674f597502a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/MemberSpec.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + internal abstract record MemberSpec + { + public MemberSpec(ISymbol member) + { + Debug.Assert(member is IPropertySymbol or IParameterSymbol); + Name = member.Name; + DefaultValueExpr = "default"; + } + + public string Name { get; } + public bool ErrorOnFailedBinding { get; protected set; } + public string DefaultValueExpr { get; protected set; } + + public required TypeSpec Type { get; init; } + public required string ConfigurationKeyName { get; init; } + + public abstract bool CanGet { get; } + public abstract bool CanSet { get; } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/NullableSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/NullableSpec.cs index c59e17e3c24240..9dcca27596e7ee 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/NullableSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/NullableSpec.cs @@ -1,23 +1,21 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using Microsoft.CodeAnalysis; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal sealed record NullableSpec : TypeSpec { - public NullableSpec(ITypeSymbol type) : base(type) { } + private readonly TypeSpec _underlyingType; - public override TypeSpecKind SpecKind => TypeSpecKind.Nullable; - - public required TypeSpec UnderlyingType { get; init; } - - public override string? InitExceptionMessage + public NullableSpec(ITypeSymbol type, TypeSpec underlyingType) : base(type) { - get => UnderlyingType.InitExceptionMessage; - set => throw new InvalidOperationException(); + _underlyingType = underlyingType; } + + public override TypeSpecKind SpecKind => TypeSpecKind.Nullable; + + public override TypeSpec EffectiveType => _underlyingType; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs index 4dbfc4a1df9bf7..1696ee099fe469 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs @@ -3,27 +3,31 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal sealed record ObjectSpec : TypeSpec { - public ObjectSpec(INamedTypeSymbol type) : base(type) - { - InitializeMethodDisplayString = $"Initialize{type.Name.Replace(".", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty)}"; - } + public ObjectSpec(INamedTypeSymbol type) : base(type) { } public override TypeSpecKind SpecKind => TypeSpecKind.Object; public override InitializationStrategy InitializationStrategy { get; set; } - public override bool CanInitialize => CanInitCompexType; + public override bool CanInitialize => CanInitComplexObject(); public Dictionary Properties { get; } = new(StringComparer.OrdinalIgnoreCase); public List ConstructorParameters { get; } = new(); - public string? InitializeMethodDisplayString { get; } + private string _displayStringWithoutSpecialCharacters; + public string DisplayStringWithoutSpecialCharacters => + _displayStringWithoutSpecialCharacters ??= $"{MinimalDisplayString.Replace(".", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty)}"; + + public override bool NeedsMemberBinding => CanInitialize && + Properties.Values.Count > 0 && + Properties.Values.Any(p => p.ShouldBind()); } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParameterSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParameterSpec.cs index a62f6080537ba2..9b5e4360c11169 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParameterSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParameterSpec.cs @@ -6,31 +6,30 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed record ParameterSpec + internal sealed record ParameterSpec : MemberSpec { - public ParameterSpec(IParameterSymbol parameter) + public ParameterSpec(IParameterSymbol parameter) : base(parameter) { - Name = parameter.Name; RefKind = parameter.RefKind; - HasExplicitDefaultValue = parameter.HasExplicitDefaultValue; - if (HasExplicitDefaultValue) + if (parameter.HasExplicitDefaultValue) { string formatted = SymbolDisplay.FormatPrimitive(parameter.ExplicitDefaultValue, quoteStrings: true, useHexadecimalNumbers: false); - DefaultValue = formatted is "null" ? "default!" : formatted; + if (formatted is not "null") + { + DefaultValueExpr = formatted; + } + } + else + { + ErrorOnFailedBinding = true; } } - public required TypeSpec Type { get; init; } - - public string Name { get; } - - public required string ConfigurationKeyName { get; init; } - public RefKind RefKind { get; } - public bool HasExplicitDefaultValue { get; init; } + public override bool CanGet => false; - public string DefaultValue { get; } = "default!"; + public override bool CanSet => true; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParsableFromStringSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParsableFromStringSpec.cs index 7f910a05c5221c..6b5bb5b61ea371 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParsableFromStringSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParsableFromStringSpec.cs @@ -19,7 +19,7 @@ public string ParseMethodName { get { - Debug.Assert(StringParsableTypeKind is not StringParsableTypeKind.ConfigValue); + Debug.Assert(StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue); _parseMethodName ??= StringParsableTypeKind is StringParsableTypeKind.ByteArray ? "ParseByteArray" @@ -34,7 +34,11 @@ public string ParseMethodName internal enum StringParsableTypeKind { None = 0, - ConfigValue = 1, + + /// + /// Declared types that can be assigned directly from IConfigurationSection.Value, i.e. string and tyepof(object). + /// + AssignFromSectionValue = 1, Enum = 2, ByteArray = 3, Integer = 4, diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/PropertySpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/PropertySpec.cs index 35d79a296eda73..584e8d570b8a9f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/PropertySpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/PropertySpec.cs @@ -5,43 +5,30 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed record PropertySpec + internal sealed record PropertySpec : MemberSpec { - public PropertySpec(IPropertySymbol property) + public PropertySpec(IPropertySymbol property) : base(property) { - Name = property.Name; - IsStatic = property.IsStatic; + IMethodSymbol? setMethod = property.SetMethod; + bool setterIsPublic = setMethod?.DeclaredAccessibility is Accessibility.Public; + bool isInitOnly = setMethod?.IsInitOnly is true; - bool setterIsPublic = property.SetMethod?.DeclaredAccessibility is Accessibility.Public; - IsInitOnly = property.SetMethod?.IsInitOnly == true; - IsRequired = property.IsRequired; - SetOnInit = setterIsPublic && (IsInitOnly || IsRequired); - CanSet = setterIsPublic && !IsInitOnly; + IsStatic = property.IsStatic; + SetOnInit = setterIsPublic && (property.IsRequired || isInitOnly); + CanSet = setterIsPublic && !isInitOnly; CanGet = property.GetMethod?.DeclaredAccessibility is Accessibility.Public; } - public required TypeSpec Type { get; init; } - public ParameterSpec? MatchingCtorParam { get; set; } - public string Name { get; } - public bool IsStatic { get; } - public bool IsRequired { get; } - - public bool IsInitOnly { get; } - public bool SetOnInit { get; } - public bool CanGet { get; } - - public bool CanSet { get; } + public override bool CanGet { get; } - public required string ConfigurationKeyName { get; init; } + public override bool CanSet { get; } - public bool ShouldBind() => - (CanGet || CanSet) && - !(!CanSet && (Type as CollectionSpec)?.InitializationStrategy is InitializationStrategy.ParameterizedConstructor); + public bool ShouldBind() => CanGet || CanSet; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs index 941320e7fa72be..88c4b24f57a5ea 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs @@ -11,13 +11,17 @@ internal sealed record SourceGenerationSpec public Dictionary> TypesForGen_ConfigurationBinder_BindMethods { get; } = new(); public HashSet PrimitivesForHelperGen { get; } = new(); - public HashSet TypeNamespaces { get; } = new() { "Microsoft.Extensions.Configuration", "System.Globalization" }; + public HashSet TypeNamespaces { get; } = new() + { + "System", + "System.CodeDom.Compiler", + "System.Globalization", + "Microsoft.Extensions.Configuration", + }; public MethodsToGen_CoreBindingHelper MethodsToGen_CoreBindingHelper { get; set; } public MethodsToGen_ConfigurationBinder MethodsToGen_ConfigurationBinder { get; set; } public MethodsToGen_Extensions_OptionsBuilder MethodsToGen_OptionsBuilderExt { get; set; } public MethodsToGen_Extensions_ServiceCollection MethodsToGen_ServiceCollectionExt { get; set; } - - public bool ShouldEmitHasChildren { get; set; } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs index f53b92960230ac..6a6292b7ebd0b4 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs @@ -20,6 +20,7 @@ public TypeSpec(ITypeSymbol type) FullyQualifiedDisplayString = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); MinimalDisplayString = type.ToDisplayString(s_minimalDisplayFormat); Name = Namespace + "." + MinimalDisplayString.Replace(".", "+"); + IsInterface = type.TypeKind is TypeKind.Interface; } public string Name { get; } @@ -40,12 +41,13 @@ public TypeSpec(ITypeSymbol type) public virtual bool CanInitialize => true; - /// - /// Location in the input compilation we picked up a call to Bind, Get, or Configure. - /// - public required Location? Location { get; init; } + public virtual bool NeedsMemberBinding { get; } - protected bool CanInitCompexType => InitializationStrategy is not InitializationStrategy.None && InitExceptionMessage is null; + public virtual TypeSpec EffectiveType => this; + + public bool IsInterface { get; } + + protected bool CanInitComplexObject() => InitializationStrategy is not InitializationStrategy.None && InitExceptionMessage is null; } internal enum TypeSpecKind diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Collections.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Collections.cs index fadaa1c0e69f1b..fb51320941bef7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Collections.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Collections.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions #endif .Configuration.Binder.Tests { - public partial class ConfigurationBinderCollectionTests + public sealed partial class ConfigurationBinderCollectionTests : ConfigurationBinderTestsBase { [Fact] public void GetList() 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 c2068feefb6d01..5e4855a55d2ff3 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 @@ -36,6 +36,15 @@ public class GenericOptions public T Value { get; set; } } + public record GenericOptionsRecord(T Value); + + public class GenericOptionsWithParamCtor + { + public GenericOptionsWithParamCtor(T value) => Value = value; + + public T Value { get; } + } + public class OptionsWithNesting { public NestedOptions Nested { get; set; } 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 75377493a423bf..d9dfefecce0c76 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -19,7 +19,17 @@ namespace Microsoft.Extensions #endif .Configuration.Binder.Tests { - public partial class ConfigurationBinderTests + public abstract class ConfigurationBinderTestsBase + { + public ConfigurationBinderTestsBase() + { +#if LAUNCH_DEBUGGER +if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launch(); } +#endif + } + } + + public sealed partial class ConfigurationBinderTests : ConfigurationBinderTestsBase { [Fact] public void BindWithNestedTypesWithReadOnlyProperties() @@ -1870,5 +1880,25 @@ public void BindRootStructIsNoOp() Assert.Equal(0, obj.Int32); Assert.False(obj.Boolean); } + + [Fact] + public void AllowsCaseInsensitiveMatch() + { + var configuration = TestHelpers.GetConfigurationFromJsonString(""" + { + "vaLue": "MyString", + } + """); + + GenericOptions obj = new(); + configuration.Bind(obj); + Assert.Equal("MyString", obj.Value); + + GenericOptionsRecord obj1 = configuration.Get>(); + Assert.Equal("MyString", obj1.Value); + + GenericOptionsWithParamCtor obj2 = configuration.Get>(); + Assert.Equal("MyString", obj2.Value); + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt index 2965e8de36ca98..f0b2ffb49b4867 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the configuration instance to a new instance of type T. @@ -12,13 +14,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; using System.Linq; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClassWithCustomCollections = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "CustomDictionary", "CustomList", "IReadOnlyList", "IReadOnlyDictionary" }); + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) { if (configuration is null) @@ -52,9 +58,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue2) + if (section.Value is string value) { - obj[section.Key!] = ParseInt(stringValue2, () => section.Path)!; + obj[section.Key] = ParseInt(value, () => section.Path); } } } @@ -68,9 +74,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue3) + if (section.Value is string value) { - obj.Add(stringValue3!); + obj.Add(value); + } + } + } + + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + obj.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref ICollection obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -89,9 +127,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue4) + if (section.Value is string value) + { + temp.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) { - temp.Add(ParseInt(stringValue4, () => section.Path)!); + obj[section.Key] = ParseInt(value, () => section.Path); + } + } + } + + public static void BindCore(IConfiguration configuration, ref IDictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + obj[section.Key] = ParseInt(value, () => section.Path); } } } @@ -110,9 +180,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue6) + if (section.Value is string value) { - temp[section.Key!] = ParseInt(stringValue6, () => section.Path)!; + temp[section.Key] = ParseInt(value, () => section.Path); } } } @@ -124,69 +194,58 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1) { - switch (section.Key) - { - case "CustomDictionary": - { - if (HasChildren(section)) - { - Program.CustomDictionary temp7 = obj.CustomDictionary; - temp7 ??= new Program.CustomDictionary(); - BindCore(section, ref temp7, binderOptions); - obj.CustomDictionary = temp7; - } - } - break; - case "CustomList": - { - if (HasChildren(section)) - { - Program.CustomList temp8 = obj.CustomList; - temp8 ??= new Program.CustomList(); - BindCore(section, ref temp8, binderOptions); - obj.CustomList = temp8; - } - } - break; - case "IReadOnlyList": - { - if (HasChildren(section)) - { - IReadOnlyList temp9 = obj.IReadOnlyList; - temp9 = temp9 is null ? new List() : new List(temp9); - BindCore(section, ref temp9, binderOptions); - obj.IReadOnlyList = temp9; - } - } - break; - case "IReadOnlyDictionary": - { - if (HasChildren(section)) - { - IReadOnlyDictionary temp10 = obj.IReadOnlyDictionary; - temp10 = temp10 is null ? new Dictionary() : temp10.ToDictionary(pair => pair.Key, pair => pair.Value); - BindCore(section, ref temp10, binderOptions); - obj.IReadOnlyDictionary = temp10; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + Program.CustomDictionary temp3 = obj.CustomDictionary; + temp3 ??= new Program.CustomDictionary(); + BindCore(section1, ref temp3, binderOptions); + obj.CustomDictionary = temp3; + } + + if (AsConfigWithChildren(configuration.GetSection("CustomList")) is IConfigurationSection section4) + { + Program.CustomList temp6 = obj.CustomList; + temp6 ??= new Program.CustomList(); + BindCore(section4, ref temp6, binderOptions); + obj.CustomList = temp6; + } + + if (AsConfigWithChildren(configuration.GetSection("IReadOnlyList")) is IConfigurationSection section7) + { + IReadOnlyList temp9 = obj.IReadOnlyList; + temp9 = temp9 is null ? new List() : new List(temp9); + BindCore(section7, ref temp9, binderOptions); + obj.IReadOnlyList = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("IReadOnlyDictionary")) is IConfigurationSection section10) + { + IReadOnlyDictionary temp12 = obj.IReadOnlyDictionary; + temp12 = temp12 is null ? new Dictionary() : temp12.ToDictionary(pair => pair.Key, pair => pair.Value); + BindCore(section10, ref temp12, binderOptions); + obj.IReadOnlyDictionary = temp12; } + } - if (temp is not null) + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClassWithCustomCollections)}: {string.Join(", ", temp)}"); + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -196,16 +255,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -223,11 +282,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt index 92b7270ef2da7b..260a7ad627951e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. @@ -18,12 +20,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { if (obj is null) @@ -33,9 +39,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue0) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue0, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -49,22 +55,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue2) + if (section.Value is string value) { - obj[section.Key!] = stringValue2!; + obj[section.Key] = value; } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) - { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - - } - public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { if (obj is null) @@ -74,12 +71,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(obj.TryGetValue(section.Key!, out Program.MyClass2? element) && element is not null)) + if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { element = new Program.MyClass2(); } - BindCore(section, ref element!, binderOptions); - obj[section.Key!] = element; + obj[section.Key] = element; } } @@ -90,81 +86,67 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value1) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue6) - { - obj.MyInt = ParseInt(stringValue6, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp7 = obj.MyList; - temp7 ??= new List(); - BindCore(section, ref temp7, binderOptions); - obj.MyList = temp7; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp8 = obj.MyDictionary; - temp8 ??= new Dictionary(); - BindCore(section, ref temp8, binderOptions); - obj.MyDictionary = temp8; - } - } - break; - case "MyComplexDictionary": - { - if (HasChildren(section)) - { - Dictionary temp9 = obj.MyComplexDictionary; - temp9 ??= new Dictionary(); - BindCore(section, ref temp9, binderOptions); - obj.MyComplexDictionary = temp9; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List temp4 = obj.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, binderOptions); + obj.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary temp7 = obj.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, binderOptions); + obj.MyDictionary = temp7; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + Dictionary temp10 = obj.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, binderOptions); + obj.MyComplexDictionary = temp10; } } - public static bool HasChildren(IConfiguration configuration) + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } + } + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -182,11 +164,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt index f017f84bd7b2da..ecc9953f810c16 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt @@ -1,7 +1,9 @@ -// +// #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. @@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { if (obj is null) @@ -27,9 +33,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue0) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue0, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -43,22 +49,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue2) + if (section.Value is string value) { - obj[section.Key!] = stringValue2!; + obj[section.Key] = value; } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) - { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - - } - public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { if (obj is null) @@ -68,12 +65,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(obj.TryGetValue(section.Key!, out Program.MyClass2? element) && element is not null)) + if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { element = new Program.MyClass2(); } - BindCore(section, ref element!, binderOptions); - obj[section.Key!] = element; + obj[section.Key] = element; } } @@ -84,88 +80,74 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value1) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue6) - { - obj.MyInt = ParseInt(stringValue6, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp7 = obj.MyList; - temp7 ??= new List(); - BindCore(section, ref temp7, binderOptions); - obj.MyList = temp7; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp8 = obj.MyDictionary; - temp8 ??= new Dictionary(); - BindCore(section, ref temp8, binderOptions); - obj.MyDictionary = temp8; - } - } - break; - case "MyComplexDictionary": - { - if (HasChildren(section)) - { - Dictionary temp9 = obj.MyComplexDictionary; - temp9 ??= new Dictionary(); - BindCore(section, ref temp9, binderOptions); - obj.MyComplexDictionary = temp9; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List temp4 = obj.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, binderOptions); + obj.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary temp7 = obj.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, binderOptions); + obj.MyDictionary = temp7; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + Dictionary temp10 = obj.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, binderOptions); + obj.MyComplexDictionary = temp10; } } - public static bool HasChildren(IConfiguration configuration) + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } + } + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt index f11815e935c67e..6132d8d5e2b0b1 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt @@ -1,7 +1,9 @@ -// +// #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. @@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { if (obj is null) @@ -27,9 +33,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue0) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue0, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -43,22 +49,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue2) + if (section.Value is string value) { - obj[section.Key!] = stringValue2!; + obj[section.Key] = value; } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) - { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - - } - public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { if (obj is null) @@ -68,12 +65,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(obj.TryGetValue(section.Key!, out Program.MyClass2? element) && element is not null)) + if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { element = new Program.MyClass2(); } - BindCore(section, ref element!, binderOptions); - obj[section.Key!] = element; + obj[section.Key] = element; } } @@ -84,81 +80,67 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value1) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue6) - { - obj.MyInt = ParseInt(stringValue6, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp7 = obj.MyList; - temp7 ??= new List(); - BindCore(section, ref temp7, binderOptions); - obj.MyList = temp7; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp8 = obj.MyDictionary; - temp8 ??= new Dictionary(); - BindCore(section, ref temp8, binderOptions); - obj.MyDictionary = temp8; - } - } - break; - case "MyComplexDictionary": - { - if (HasChildren(section)) - { - Dictionary temp9 = obj.MyComplexDictionary; - temp9 ??= new Dictionary(); - BindCore(section, ref temp9, binderOptions); - obj.MyComplexDictionary = temp9; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List temp4 = obj.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, binderOptions); + obj.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary temp7 = obj.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, binderOptions); + obj.MyDictionary = temp7; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + Dictionary temp10 = obj.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, binderOptions); + obj.MyComplexDictionary = temp10; } } - public static bool HasChildren(IConfiguration configuration) + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } + } + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -176,11 +158,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt index d96b24a05bb5c6..00570279261ade 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt @@ -1,7 +1,9 @@ -// +// #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. @@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { if (obj is null) @@ -27,9 +33,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue0) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue0, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -43,22 +49,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue2) + if (section.Value is string value) { - obj[section.Key!] = stringValue2!; + obj[section.Key] = value; } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) - { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - - } - public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { if (obj is null) @@ -68,12 +65,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(obj.TryGetValue(section.Key!, out Program.MyClass2? element) && element is not null)) + if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { element = new Program.MyClass2(); } - BindCore(section, ref element!, binderOptions); - obj[section.Key!] = element; + obj[section.Key] = element; } } @@ -84,88 +80,74 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value1) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue6) - { - obj.MyInt = ParseInt(stringValue6, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp7 = obj.MyList; - temp7 ??= new List(); - BindCore(section, ref temp7, binderOptions); - obj.MyList = temp7; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp8 = obj.MyDictionary; - temp8 ??= new Dictionary(); - BindCore(section, ref temp8, binderOptions); - obj.MyDictionary = temp8; - } - } - break; - case "MyComplexDictionary": - { - if (HasChildren(section)) - { - Dictionary temp9 = obj.MyComplexDictionary; - temp9 ??= new Dictionary(); - BindCore(section, ref temp9, binderOptions); - obj.MyComplexDictionary = temp9; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List temp4 = obj.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, binderOptions); + obj.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary temp7 = obj.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, binderOptions); + obj.MyDictionary = temp7; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + Dictionary temp10 = obj.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, binderOptions); + obj.MyComplexDictionary = temp10; } } - public static bool HasChildren(IConfiguration configuration) + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } + } + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt index ea9c6ba494ee00..edc299c57eb447 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the configuration instance to a new instance of type T. @@ -21,12 +23,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" }); + private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) { if (configuration is null) @@ -67,9 +74,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue2) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue2, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -81,11 +88,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - var temp3 = new List(); - BindCore(configuration, ref temp3, binderOptions); + var temp2 = new List(); + BindCore(configuration, ref temp2, binderOptions); int originalCount = obj.Length; - Array.Resize(ref obj, originalCount + temp3.Count); - temp3.CopyTo(obj, originalCount); + Array.Resize(ref obj, originalCount + temp2.Count); + temp2.CopyTo(obj, originalCount); } public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) @@ -97,9 +104,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue6) + if (section.Value is string value) { - obj[section.Key!] = stringValue6!; + obj[section.Key] = value; } } } @@ -111,71 +118,37 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value5) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue8) - { - obj.MyInt = ParseInt(stringValue8, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp9 = obj.MyList; - temp9 ??= new List(); - BindCore(section, ref temp9, binderOptions); - obj.MyList = temp9; - } - } - break; - case "MyArray": - { - if (HasChildren(section)) - { - int[] temp10 = obj.MyArray; - temp10 ??= new int[0]; - BindCore(section, ref temp10, binderOptions); - obj.MyArray = temp10; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp11 = obj.MyDictionary; - temp11 ??= new Dictionary(); - BindCore(section, ref temp11, binderOptions); - obj.MyDictionary = temp11; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) + { + List temp8 = obj.MyList; + temp8 ??= new List(); + BindCore(section6, ref temp8, binderOptions); + obj.MyList = temp8; + } + + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section9) + { + int[] temp11 = obj.MyArray; + temp11 ??= new int[0]; + BindCore(section9, ref temp11, binderOptions); + obj.MyArray = temp11; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section12) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + Dictionary temp14 = obj.MyDictionary; + temp14 ??= new Dictionary(); + BindCore(section12, ref temp14, binderOptions); + obj.MyDictionary = temp14; } } @@ -186,33 +159,31 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value15) { - switch (section.Key) - { - case "MyInt": - { - if (configuration["MyInt"] is string stringValue12) - { - obj.MyInt = ParseInt(stringValue12, () => section.Path)!; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value15, () => configuration.GetSection("MyInt").Path); } + } - if (temp is not null) + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}"); + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -222,16 +193,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -249,11 +220,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt index 2d5cb75ed42556..c9d49faa937244 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Extracts the value with the specified key and converts it to the specified type. @@ -21,10 +23,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { @@ -35,46 +39,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); + if (section.Value is not string value) + { + return null; + } + if (type == typeof(int)) { - if (section.Value is string stringValue0) - { - return ParseInt(stringValue0, () => section.Path); - } + return ParseInt(value, () => section.Path); } if (type == typeof(bool?)) { - if (section.Value is string stringValue1) - { - return ParseBool(stringValue1, () => section.Path); - } + return ParseBool(value, () => section.Path); } if (type == typeof(byte[])) { - if (section.Value is string stringValue2) - { - return ParseByteArray(stringValue2, () => section.Path); - } + return ParseByteArray(value, () => section.Path); } if (type == typeof(CultureInfo)) { - if (section.Value is string stringValue3) - { - return ParseCultureInfo(stringValue3, () => section.Path); - } + return ParseCultureInfo(value, () => section.Path); } return null; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -82,11 +79,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static bool ParseBool(string stringValue, Func getPath) + public static bool ParseBool(string value, Func getPath) { try { - return bool.Parse(stringValue); + return bool.Parse(value); } catch (Exception exception) { @@ -94,11 +91,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static byte[] ParseByteArray(string stringValue, Func getPath) + public static byte[] ParseByteArray(string value, Func getPath) { try { - return Convert.FromBase64String(stringValue); + return Convert.FromBase64String(value); } catch (Exception exception) { @@ -106,11 +103,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static CultureInfo ParseCultureInfo(string stringValue, Func getPath) + public static CultureInfo ParseCultureInfo(string value, Func getPath) { try { - return CultureInfo.GetCultureInfo(stringValue); + return CultureInfo.GetCultureInfo(value); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt index 19ecaa035c9402..17c963bd980a70 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Extracts the value with the specified key and converts it to the specified type. @@ -12,10 +14,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { @@ -26,22 +30,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); + if (section.Value is not string value) + { + return null; + } + if (type == typeof(int)) { - if (section.Value is string stringValue0) - { - return ParseInt(stringValue0, () => section.Path); - } + return ParseInt(value, () => section.Path); } return null; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt index dac919263ffda0..1148109b9f5a81 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Extracts the value with the specified key and converts it to the specified type. @@ -12,10 +14,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { @@ -26,22 +30,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); + if (section.Value is not string value) + { + return null; + } + if (type == typeof(int)) { - if (section.Value is string stringValue0) - { - return ParseInt(stringValue0, () => section.Path); - } + return ParseInt(value, () => section.Path); } return null; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt index 4e7dbe7bd980c5..c833b20f18dcfe 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Extracts the value with the specified key and converts it to the specified type. @@ -12,10 +14,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { @@ -26,22 +30,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); + if (section.Value is not string value) + { + return null; + } + if (type == typeof(bool?)) { - if (section.Value is string stringValue0) - { - return ParseBool(stringValue0, () => section.Path); - } + return ParseBool(value, () => section.Path); } return null; } - public static bool ParseBool(string stringValue, Func getPath) + public static bool ParseBool(string value, Func getPath) { try { - return bool.Parse(stringValue); + return bool.Parse(value); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt index e7eb6cc93f9049..f773f79ce6c2c0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Extracts the value with the specified key and converts it to the specified type. @@ -11,10 +13,13 @@ internal static class GeneratedConfigurationBinder namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; + using System; + using System.CodeDom.Compiler; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { @@ -25,22 +30,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); + if (section.Value is not string value) + { + return null; + } + if (type == typeof(CultureInfo)) { - if (section.Value is string stringValue0) - { - return ParseCultureInfo(stringValue0, () => section.Path); - } + return ParseCultureInfo(value, () => section.Path); } return null; } - public static CultureInfo ParseCultureInfo(string stringValue, Func getPath) + public static CultureInfo ParseCultureInfo(string value, Func getPath) { try { - return CultureInfo.GetCultureInfo(stringValue); + return CultureInfo.GetCultureInfo(value); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt index 8bfc62e6b62908..92768a54a644d4 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt @@ -1,7 +1,9 @@ -// +// #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the configuration instance to a new instance of type T. @@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" }); + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) { if (configuration is null) @@ -51,9 +57,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue1) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue1, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -65,11 +71,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - var temp2 = new List(); - BindCore(configuration, ref temp2, binderOptions); + var temp1 = new List(); + BindCore(configuration, ref temp1, binderOptions); int originalCount = obj.Length; - Array.Resize(ref obj, originalCount + temp2.Count); - temp2.CopyTo(obj, originalCount); + Array.Resize(ref obj, originalCount + temp1.Count); + temp1.CopyTo(obj, originalCount); } public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) @@ -81,9 +87,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue5) + if (section.Value is string value) { - obj[section.Key!] = stringValue5!; + obj[section.Key] = value; } } } @@ -95,71 +101,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value4) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue7) - { - obj.MyInt = ParseInt(stringValue7, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp8 = obj.MyList; - temp8 ??= new List(); - BindCore(section, ref temp8, binderOptions); - obj.MyList = temp8; - } - } - break; - case "MyArray": - { - if (HasChildren(section)) - { - int[] temp9 = obj.MyArray; - temp9 ??= new int[0]; - BindCore(section, ref temp9, binderOptions); - obj.MyArray = temp9; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp10 = obj.MyDictionary; - temp10 ??= new Dictionary(); - BindCore(section, ref temp10, binderOptions); - obj.MyDictionary = temp10; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, binderOptions); + obj.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8) + { + int[] temp10 = obj.MyArray; + temp10 ??= new int[0]; + BindCore(section8, ref temp10, binderOptions); + obj.MyArray = temp10; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + Dictionary temp13 = obj.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, binderOptions); + obj.MyDictionary = temp13; + } + } + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -169,16 +161,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -196,11 +188,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt index 72d5f029ea51cb..528049d9617e29 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the configuration instance to a new instance of type T. @@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" }); + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) { if (configuration is null) @@ -51,9 +57,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue1) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue1, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -65,11 +71,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - var temp2 = new List(); - BindCore(configuration, ref temp2, binderOptions); + var temp1 = new List(); + BindCore(configuration, ref temp1, binderOptions); int originalCount = obj.Length; - Array.Resize(ref obj, originalCount + temp2.Count); - temp2.CopyTo(obj, originalCount); + Array.Resize(ref obj, originalCount + temp1.Count); + temp1.CopyTo(obj, originalCount); } public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) @@ -81,9 +87,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue5) + if (section.Value is string value) { - obj[section.Key!] = stringValue5!; + obj[section.Key] = value; } } } @@ -95,71 +101,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value4) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue7) - { - obj.MyInt = ParseInt(stringValue7, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp8 = obj.MyList; - temp8 ??= new List(); - BindCore(section, ref temp8, binderOptions); - obj.MyList = temp8; - } - } - break; - case "MyArray": - { - if (HasChildren(section)) - { - int[] temp9 = obj.MyArray; - temp9 ??= new int[0]; - BindCore(section, ref temp9, binderOptions); - obj.MyArray = temp9; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp10 = obj.MyDictionary; - temp10 ??= new Dictionary(); - BindCore(section, ref temp10, binderOptions); - obj.MyDictionary = temp10; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, binderOptions); + obj.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8) + { + int[] temp10 = obj.MyArray; + temp10 ??= new int[0]; + BindCore(section8, ref temp10, binderOptions); + obj.MyArray = temp10; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + Dictionary temp13 = obj.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, binderOptions); + obj.MyDictionary = temp13; + } + } + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -169,16 +161,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -196,11 +188,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt index 65cd132591bc6e..01a865f14b23ca 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the configuration instance to a new instance of type T. @@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) { if (configuration is null) @@ -49,33 +55,31 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) { - switch (section.Key) - { - case "MyInt": - { - if (configuration["MyInt"] is string stringValue1) - { - obj.MyInt = ParseInt(stringValue1, () => section.Path)!; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } + } - if (temp is not null) + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}"); + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -85,16 +89,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -112,11 +116,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt index f5044a5a4140b9..620a6049fbb855 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the configuration instance to a new instance of type T. @@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) { if (configuration is null) @@ -49,33 +55,31 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) { - switch (section.Key) - { - case "MyInt": - { - if (configuration["MyInt"] is string stringValue1) - { - obj.MyInt = ParseInt(stringValue1, () => section.Path)!; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } + } - if (temp is not null) + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}"); + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -85,16 +89,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -112,11 +116,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt index 4c2a501d470148..2c61207f45369e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt @@ -1,7 +1,9 @@ -// +// #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedOptionsBuilderBinder { /// Registers the dependency injection container to bind against the obtained from the DI service provider. @@ -33,12 +35,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); + public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) @@ -72,9 +78,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue1) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue1, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -86,49 +92,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value2) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue3) - { - obj.MyInt = ParseInt(stringValue3, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp4 = obj.MyList; - temp4 ??= new List(); - BindCore(section, ref temp4, binderOptions); - obj.MyList = temp4; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + List temp5 = obj.MyList; + temp5 ??= new List(); + BindCore(section3, ref temp5, binderOptions); + obj.MyList = temp5; + } + } + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -138,16 +136,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -165,11 +163,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt index 56eb29dbd9518b..a496d89b0000dd 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedOptionsBuilderBinder { /// Registers a configuration instance which will bind against. @@ -10,7 +12,6 @@ internal static class GeneratedOptionsBuilderBinder return global::GeneratedOptionsBuilderBinder.Bind(optionsBuilder, configuration, configureOptions: null); } - /// Registers a configuration instance which will bind against. public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class { @@ -25,6 +26,7 @@ internal static class GeneratedOptionsBuilderBinder } /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedServiceCollectionBinder { /// Registers a configuration instance which TOptions will bind against. @@ -51,12 +53,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); + public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) @@ -90,9 +96,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue1) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue1, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -104,49 +110,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value2) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue3) - { - obj.MyInt = ParseInt(stringValue3, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp4 = obj.MyList; - temp4 ??= new List(); - BindCore(section, ref temp4, binderOptions); - obj.MyList = temp4; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + List temp5 = obj.MyList; + temp5 ??= new List(); + BindCore(section3, ref temp5, binderOptions); + obj.MyList = temp5; } + } - if (temp is not null) + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -156,16 +154,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -183,11 +181,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt index dd47a061e76592..23a3f5028245ca 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedOptionsBuilderBinder { /// Registers a configuration instance which will bind against. @@ -18,6 +20,7 @@ internal static class GeneratedOptionsBuilderBinder } /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedServiceCollectionBinder { /// Registers a configuration instance which TOptions will bind against. @@ -44,12 +47,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); + public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) @@ -83,9 +90,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue1) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue1, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -97,49 +104,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value2) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue3) - { - obj.MyInt = ParseInt(stringValue3, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp4 = obj.MyList; - temp4 ??= new List(); - BindCore(section, ref temp4, binderOptions); - obj.MyList = temp4; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + List temp5 = obj.MyList; + temp5 ??= new List(); + BindCore(section3, ref temp5, binderOptions); + obj.MyList = temp5; + } + } + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -149,16 +148,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -176,11 +175,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt index 4d8f1bf3b78931..c38f1fd39c5256 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedConfigurationBinder { /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. @@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Prop0", "Prop1", "Prop2", "Prop3", "Prop4", "Prop5", "Prop6", "Prop8", "Prop9", "Prop10", "Prop13", "Prop14", "Prop15", "Prop16", "Prop17", "Prop19", "Prop20", "Prop21", "Prop23", "Prop24", "Prop25", "Prop26", "Prop27", "Prop7", "Prop11", "Prop12", "Prop18", "Prop22" }); + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { if (obj is null) @@ -25,251 +31,168 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["Prop0"] is string value0) { - switch (section.Key) - { - case "Prop0": - { - if (configuration["Prop0"] is string stringValue0) - { - obj.Prop0 = ParseBool(stringValue0, () => section.Path)!; - } - } - break; - case "Prop1": - { - if (configuration["Prop1"] is string stringValue1) - { - obj.Prop1 = ParseByte(stringValue1, () => section.Path)!; - } - } - break; - case "Prop2": - { - if (configuration["Prop2"] is string stringValue2) - { - obj.Prop2 = ParseSbyte(stringValue2, () => section.Path)!; - } - } - break; - case "Prop3": - { - if (configuration["Prop3"] is string stringValue3) - { - obj.Prop3 = ParseChar(stringValue3, () => section.Path)!; - } - } - break; - case "Prop4": - { - if (configuration["Prop4"] is string stringValue4) - { - obj.Prop4 = ParseDouble(stringValue4, () => section.Path)!; - } - } - break; - case "Prop5": - { - obj.Prop5 = configuration["Prop5"]!; - } - break; - case "Prop6": - { - if (configuration["Prop6"] is string stringValue6) - { - obj.Prop6 = ParseInt(stringValue6, () => section.Path)!; - } - } - break; - case "Prop8": - { - if (configuration["Prop8"] is string stringValue7) - { - obj.Prop8 = ParseShort(stringValue7, () => section.Path)!; - } - } - break; - case "Prop9": - { - if (configuration["Prop9"] is string stringValue8) - { - obj.Prop9 = ParseLong(stringValue8, () => section.Path)!; - } - } - break; - case "Prop10": - { - if (configuration["Prop10"] is string stringValue9) - { - obj.Prop10 = ParseFloat(stringValue9, () => section.Path)!; - } - } - break; - case "Prop13": - { - if (configuration["Prop13"] is string stringValue10) - { - obj.Prop13 = ParseUshort(stringValue10, () => section.Path)!; - } - } - break; - case "Prop14": - { - if (configuration["Prop14"] is string stringValue11) - { - obj.Prop14 = ParseUint(stringValue11, () => section.Path)!; - } - } - break; - case "Prop15": - { - if (configuration["Prop15"] is string stringValue12) - { - obj.Prop15 = ParseUlong(stringValue12, () => section.Path)!; - } - } - break; - case "Prop16": - { - obj.Prop16 = configuration["Prop16"]!; - } - break; - case "Prop17": - { - if (configuration["Prop17"] is string stringValue14) - { - obj.Prop17 = ParseCultureInfo(stringValue14, () => section.Path)!; - } - } - break; - case "Prop19": - { - if (configuration["Prop19"] is string stringValue15) - { - obj.Prop19 = ParseDateTime(stringValue15, () => section.Path)!; - } - } - break; - case "Prop20": - { - if (configuration["Prop20"] is string stringValue16) - { - obj.Prop20 = ParseDateTimeOffset(stringValue16, () => section.Path)!; - } - } - break; - case "Prop21": - { - if (configuration["Prop21"] is string stringValue17) - { - obj.Prop21 = ParseDecimal(stringValue17, () => section.Path)!; - } - } - break; - case "Prop23": - { - if (configuration["Prop23"] is string stringValue18) - { - obj.Prop23 = ParseInt(stringValue18, () => section.Path)!; - } - } - break; - case "Prop24": - { - if (configuration["Prop24"] is string stringValue19) - { - obj.Prop24 = ParseDateTime(stringValue19, () => section.Path)!; - } - } - break; - case "Prop25": - { - if (configuration["Prop25"] is string stringValue20) - { - obj.Prop25 = ParseUri(stringValue20, () => section.Path)!; - } - } - break; - case "Prop26": - { - if (configuration["Prop26"] is string stringValue21) - { - obj.Prop26 = ParseVersion(stringValue21, () => section.Path)!; - } - } - break; - case "Prop27": - { - if (configuration["Prop27"] is string stringValue22) - { - obj.Prop27 = ParseDayOfWeek(stringValue22, () => section.Path)!; - } - } - break; - case "Prop7": - { - if (configuration["Prop7"] is string stringValue23) - { - obj.Prop7 = ParseInt128(stringValue23, () => section.Path)!; - } - } - break; - case "Prop11": - { - if (configuration["Prop11"] is string stringValue24) - { - obj.Prop11 = ParseHalf(stringValue24, () => section.Path)!; - } - } - break; - case "Prop12": - { - if (configuration["Prop12"] is string stringValue25) - { - obj.Prop12 = ParseUInt128(stringValue25, () => section.Path)!; - } - } - break; - case "Prop18": - { - if (configuration["Prop18"] is string stringValue26) - { - obj.Prop18 = ParseDateOnly(stringValue26, () => section.Path)!; - } - } - break; - case "Prop22": - { - if (configuration["Prop22"] is string stringValue27) - { - obj.Prop22 = ParseByteArray(stringValue27, () => section.Path)!; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.Prop0 = ParseBool(value0, () => configuration.GetSection("Prop0").Path); + } + + if (configuration["Prop1"] is string value1) + { + obj.Prop1 = ParseByte(value1, () => configuration.GetSection("Prop1").Path); + } + + if (configuration["Prop2"] is string value2) + { + obj.Prop2 = ParseSbyte(value2, () => configuration.GetSection("Prop2").Path); + } + + if (configuration["Prop3"] is string value3) + { + obj.Prop3 = ParseChar(value3, () => configuration.GetSection("Prop3").Path); + } + + if (configuration["Prop4"] is string value4) + { + obj.Prop4 = ParseDouble(value4, () => configuration.GetSection("Prop4").Path); + } + + obj.Prop5 = configuration["Prop5"]!; + + if (configuration["Prop6"] is string value6) + { + obj.Prop6 = ParseInt(value6, () => configuration.GetSection("Prop6").Path); + } + + if (configuration["Prop8"] is string value7) + { + obj.Prop8 = ParseShort(value7, () => configuration.GetSection("Prop8").Path); + } + + if (configuration["Prop9"] is string value8) + { + obj.Prop9 = ParseLong(value8, () => configuration.GetSection("Prop9").Path); + } + + if (configuration["Prop10"] is string value9) + { + obj.Prop10 = ParseFloat(value9, () => configuration.GetSection("Prop10").Path); + } + + if (configuration["Prop13"] is string value10) + { + obj.Prop13 = ParseUshort(value10, () => configuration.GetSection("Prop13").Path); + } + + if (configuration["Prop14"] is string value11) + { + obj.Prop14 = ParseUint(value11, () => configuration.GetSection("Prop14").Path); + } + + if (configuration["Prop15"] is string value12) + { + obj.Prop15 = ParseUlong(value12, () => configuration.GetSection("Prop15").Path); } - if (temp is not null) + obj.Prop16 = configuration["Prop16"]!; + + if (configuration["Prop17"] is string value14) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + obj.Prop17 = ParseCultureInfo(value14, () => configuration.GetSection("Prop17").Path); + } + + if (configuration["Prop19"] is string value15) + { + obj.Prop19 = ParseDateTime(value15, () => configuration.GetSection("Prop19").Path); + } + + if (configuration["Prop20"] is string value16) + { + obj.Prop20 = ParseDateTimeOffset(value16, () => configuration.GetSection("Prop20").Path); + } + + if (configuration["Prop21"] is string value17) + { + obj.Prop21 = ParseDecimal(value17, () => configuration.GetSection("Prop21").Path); + } + + if (configuration["Prop23"] is string value18) + { + obj.Prop23 = ParseInt(value18, () => configuration.GetSection("Prop23").Path); + } + + if (configuration["Prop24"] is string value19) + { + obj.Prop24 = ParseDateTime(value19, () => configuration.GetSection("Prop24").Path); + } + + if (configuration["Prop25"] is string value20) + { + obj.Prop25 = ParseUri(value20, () => configuration.GetSection("Prop25").Path); + } + + if (configuration["Prop26"] is string value21) + { + obj.Prop26 = ParseVersion(value21, () => configuration.GetSection("Prop26").Path); + } + + if (configuration["Prop27"] is string value22) + { + obj.Prop27 = ParseDayOfWeek(value22, () => configuration.GetSection("Prop27").Path); + } + + if (configuration["Prop7"] is string value23) + { + obj.Prop7 = ParseInt128(value23, () => configuration.GetSection("Prop7").Path); + } + + if (configuration["Prop11"] is string value24) + { + obj.Prop11 = ParseHalf(value24, () => configuration.GetSection("Prop11").Path); + } + + if (configuration["Prop12"] is string value25) + { + obj.Prop12 = ParseUInt128(value25, () => configuration.GetSection("Prop12").Path); + } + + if (configuration["Prop18"] is string value26) + { + obj.Prop18 = ParseDateOnly(value26, () => configuration.GetSection("Prop18").Path); + } + + if (configuration["Prop22"] is string value27) + { + obj.Prop22 = ParseByteArray(value27, () => configuration.GetSection("Prop22").Path); + } + } + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } - public static bool ParseBool(string stringValue, Func getPath) + public static bool ParseBool(string value, Func getPath) { try { - return bool.Parse(stringValue); + return bool.Parse(value); } catch (Exception exception) { @@ -277,11 +200,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static byte ParseByte(string stringValue, Func getPath) + public static byte ParseByte(string value, Func getPath) { try { - return byte.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return byte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -289,11 +212,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static sbyte ParseSbyte(string stringValue, Func getPath) + public static sbyte ParseSbyte(string value, Func getPath) { try { - return sbyte.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return sbyte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -301,11 +224,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static char ParseChar(string stringValue, Func getPath) + public static char ParseChar(string value, Func getPath) { try { - return char.Parse(stringValue); + return char.Parse(value); } catch (Exception exception) { @@ -313,11 +236,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static double ParseDouble(string stringValue, Func getPath) + public static double ParseDouble(string value, Func getPath) { try { - return double.Parse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture); + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -325,11 +248,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -337,11 +260,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static short ParseShort(string stringValue, Func getPath) + public static short ParseShort(string value, Func getPath) { try { - return short.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return short.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -349,11 +272,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static long ParseLong(string stringValue, Func getPath) + public static long ParseLong(string value, Func getPath) { try { - return long.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -361,11 +284,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static float ParseFloat(string stringValue, Func getPath) + public static float ParseFloat(string value, Func getPath) { try { - return float.Parse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture); + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -373,11 +296,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static ushort ParseUshort(string stringValue, Func getPath) + public static ushort ParseUshort(string value, Func getPath) { try { - return ushort.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return ushort.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -385,11 +308,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static uint ParseUint(string stringValue, Func getPath) + public static uint ParseUint(string value, Func getPath) { try { - return uint.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return uint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -397,11 +320,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static ulong ParseUlong(string stringValue, Func getPath) + public static ulong ParseUlong(string value, Func getPath) { try { - return ulong.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return ulong.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -409,11 +332,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static CultureInfo ParseCultureInfo(string stringValue, Func getPath) + public static CultureInfo ParseCultureInfo(string value, Func getPath) { try { - return CultureInfo.GetCultureInfo(stringValue); + return CultureInfo.GetCultureInfo(value); } catch (Exception exception) { @@ -421,11 +344,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static DateTime ParseDateTime(string stringValue, Func getPath) + public static DateTime ParseDateTime(string value, Func getPath) { try { - return DateTime.Parse(stringValue, CultureInfo.InvariantCulture); + return DateTime.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -433,11 +356,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static DateTimeOffset ParseDateTimeOffset(string stringValue, Func getPath) + public static DateTimeOffset ParseDateTimeOffset(string value, Func getPath) { try { - return DateTimeOffset.Parse(stringValue, CultureInfo.InvariantCulture); + return DateTimeOffset.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -445,11 +368,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static decimal ParseDecimal(string stringValue, Func getPath) + public static decimal ParseDecimal(string value, Func getPath) { try { - return decimal.Parse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture); + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -457,11 +380,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static TimeSpan ParseTimeSpan(string stringValue, Func getPath) + public static TimeSpan ParseTimeSpan(string value, Func getPath) { try { - return TimeSpan.Parse(stringValue, CultureInfo.InvariantCulture); + return TimeSpan.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -469,11 +392,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static Guid ParseGuid(string stringValue, Func getPath) + public static Guid ParseGuid(string value, Func getPath) { try { - return Guid.Parse(stringValue); + return Guid.Parse(value); } catch (Exception exception) { @@ -481,11 +404,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static Uri ParseUri(string stringValue, Func getPath) + public static Uri ParseUri(string value, Func getPath) { try { - return new Uri(stringValue, UriKind.RelativeOrAbsolute); + return new Uri(value, UriKind.RelativeOrAbsolute); } catch (Exception exception) { @@ -493,11 +416,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static Version ParseVersion(string stringValue, Func getPath) + public static Version ParseVersion(string value, Func getPath) { try { - return Version.Parse(stringValue); + return Version.Parse(value); } catch (Exception exception) { @@ -505,11 +428,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static DayOfWeek ParseDayOfWeek(string stringValue, Func getPath) + public static DayOfWeek ParseDayOfWeek(string value, Func getPath) { try { - return (DayOfWeek)Enum.Parse(typeof(DayOfWeek), stringValue, ignoreCase: true); + return (DayOfWeek)Enum.Parse(typeof(DayOfWeek), value, ignoreCase: true); } catch (Exception exception) { @@ -517,11 +440,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static Int128 ParseInt128(string stringValue, Func getPath) + public static Int128 ParseInt128(string value, Func getPath) { try { - return Int128.Parse(stringValue, CultureInfo.InvariantCulture); + return Int128.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -529,11 +452,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static Half ParseHalf(string stringValue, Func getPath) + public static Half ParseHalf(string value, Func getPath) { try { - return Half.Parse(stringValue, CultureInfo.InvariantCulture); + return Half.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -541,11 +464,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static UInt128 ParseUInt128(string stringValue, Func getPath) + public static UInt128 ParseUInt128(string value, Func getPath) { try { - return UInt128.Parse(stringValue, CultureInfo.InvariantCulture); + return UInt128.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -553,11 +476,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static DateOnly ParseDateOnly(string stringValue, Func getPath) + public static DateOnly ParseDateOnly(string value, Func getPath) { try { - return DateOnly.Parse(stringValue, CultureInfo.InvariantCulture); + return DateOnly.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -565,11 +488,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static TimeOnly ParseTimeOnly(string stringValue, Func getPath) + public static TimeOnly ParseTimeOnly(string value, Func getPath) { try { - return TimeOnly.Parse(stringValue, CultureInfo.InvariantCulture); + return TimeOnly.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { @@ -577,11 +500,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static byte[] ParseByteArray(string stringValue, Func getPath) + public static byte[] ParseByteArray(string value, Func getPath) { try { - return Convert.FromBase64String(stringValue); + return Convert.FromBase64String(value); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt index 52cb0f69d7c316..c5717e3fc83332 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedServiceCollectionBinder { /// Registers a configuration instance which TOptions will bind against. @@ -33,12 +35,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); + public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) @@ -72,9 +79,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue1) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue1, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -86,33 +93,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) - { - switch (section.Key) - { - case "MyInt": - { - if (configuration["MyInt"] is string stringValue2) - { - obj.MyInt = ParseInt(stringValue2, () => section.Path)!; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } - } + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (temp is not null) + if (configuration["MyInt"] is string value1) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}"); + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } } @@ -125,9 +110,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - var element = new Program.MyClass2(); - BindCore(section, ref element, binderOptions); - obj.Add(element); + var value = new Program.MyClass2(); + BindCore(section, ref value, binderOptions); + obj.Add(value); } } @@ -140,9 +125,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue5) + if (section.Value is string value) { - obj[section.Key!] = stringValue5!; + obj[section.Key] = value; } } } @@ -154,71 +139,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value4) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue7) - { - obj.MyInt = ParseInt(stringValue7, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp8 = obj.MyList; - temp8 ??= new List(); - BindCore(section, ref temp8, binderOptions); - obj.MyList = temp8; - } - } - break; - case "MyList2": - { - if (HasChildren(section)) - { - List temp9 = obj.MyList2; - temp9 ??= new List(); - BindCore(section, ref temp9, binderOptions); - obj.MyList2 = temp9; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp10 = obj.MyDictionary; - temp10 ??= new Dictionary(); - BindCore(section, ref temp10, binderOptions); - obj.MyDictionary = temp10; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, binderOptions); + obj.MyList = temp7; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + List temp10 = obj.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, binderOptions); + obj.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary temp13 = obj.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, binderOptions); + obj.MyDictionary = temp13; + } + } + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -228,16 +199,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -255,11 +226,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt index 9c7bc2ab95e095..1b1405c2fc5c5f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedServiceCollectionBinder { /// Registers a configuration instance which TOptions will bind against. @@ -33,12 +35,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); + public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) @@ -72,9 +79,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue1) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue1, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -86,33 +93,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) - { - switch (section.Key) - { - case "MyInt": - { - if (configuration["MyInt"] is string stringValue2) - { - obj.MyInt = ParseInt(stringValue2, () => section.Path)!; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } - } + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (temp is not null) + if (configuration["MyInt"] is string value1) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}"); + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } } @@ -125,9 +110,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - var element = new Program.MyClass2(); - BindCore(section, ref element, binderOptions); - obj.Add(element); + var value = new Program.MyClass2(); + BindCore(section, ref value, binderOptions); + obj.Add(value); } } @@ -140,9 +125,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue5) + if (section.Value is string value) { - obj[section.Key!] = stringValue5!; + obj[section.Key] = value; } } } @@ -154,71 +139,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value4) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue7) - { - obj.MyInt = ParseInt(stringValue7, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp8 = obj.MyList; - temp8 ??= new List(); - BindCore(section, ref temp8, binderOptions); - obj.MyList = temp8; - } - } - break; - case "MyList2": - { - if (HasChildren(section)) - { - List temp9 = obj.MyList2; - temp9 ??= new List(); - BindCore(section, ref temp9, binderOptions); - obj.MyList2 = temp9; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp10 = obj.MyDictionary; - temp10 ??= new Dictionary(); - BindCore(section, ref temp10, binderOptions); - obj.MyDictionary = temp10; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, binderOptions); + obj.MyList = temp7; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + List temp10 = obj.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, binderOptions); + obj.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary temp13 = obj.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, binderOptions); + obj.MyDictionary = temp13; + } + } + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -228,16 +199,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -255,11 +226,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt index 016995beb79425..26cb4aacd72d50 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedServiceCollectionBinder { /// Registers a configuration instance which TOptions will bind against. @@ -33,12 +35,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); + public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) @@ -72,9 +79,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue1) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue1, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -86,33 +93,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) - { - switch (section.Key) - { - case "MyInt": - { - if (configuration["MyInt"] is string stringValue2) - { - obj.MyInt = ParseInt(stringValue2, () => section.Path)!; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } - } + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (temp is not null) + if (configuration["MyInt"] is string value1) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}"); + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } } @@ -125,9 +110,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - var element = new Program.MyClass2(); - BindCore(section, ref element, binderOptions); - obj.Add(element); + var value = new Program.MyClass2(); + BindCore(section, ref value, binderOptions); + obj.Add(value); } } @@ -140,9 +125,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue5) + if (section.Value is string value) { - obj[section.Key!] = stringValue5!; + obj[section.Key] = value; } } } @@ -154,71 +139,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value4) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue7) - { - obj.MyInt = ParseInt(stringValue7, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp8 = obj.MyList; - temp8 ??= new List(); - BindCore(section, ref temp8, binderOptions); - obj.MyList = temp8; - } - } - break; - case "MyList2": - { - if (HasChildren(section)) - { - List temp9 = obj.MyList2; - temp9 ??= new List(); - BindCore(section, ref temp9, binderOptions); - obj.MyList2 = temp9; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp10 = obj.MyDictionary; - temp10 ??= new Dictionary(); - BindCore(section, ref temp10, binderOptions); - obj.MyDictionary = temp10; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, binderOptions); + obj.MyList = temp7; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + List temp10 = obj.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, binderOptions); + obj.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary temp13 = obj.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, binderOptions); + obj.MyDictionary = temp13; + } + } + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -228,16 +199,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -255,11 +226,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt index 312a5534c0f44c..53f8ead3ed5877 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt @@ -1,7 +1,9 @@ // #nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. /// Generated helper providing an AOT and linking compatible implementation for configuration binding. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] internal static class GeneratedServiceCollectionBinder { /// Registers a configuration instance which TOptions will bind against. @@ -27,12 +29,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using System; + using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; /// Provide core binding logic. - internal static class CoreBindingHelper + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class CoreBindingHelper { + private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); + public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) @@ -66,9 +73,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue1) + if (section.Value is string value) { - obj.Add(ParseInt(stringValue1, () => section.Path)!); + obj.Add(ParseInt(value, () => section.Path)); } } } @@ -80,33 +87,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) - { - switch (section.Key) - { - case "MyInt": - { - if (configuration["MyInt"] is string stringValue2) - { - obj.MyInt = ParseInt(stringValue2, () => section.Path)!; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } - } + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (temp is not null) + if (configuration["MyInt"] is string value1) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}"); + obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } } @@ -119,9 +104,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - var element = new Program.MyClass2(); - BindCore(section, ref element, binderOptions); - obj.Add(element); + var value = new Program.MyClass2(); + BindCore(section, ref value, binderOptions); + obj.Add(value); } } @@ -134,9 +119,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string stringValue5) + if (section.Value is string value) { - obj[section.Key!] = stringValue5!; + obj[section.Key] = value; } } } @@ -148,71 +133,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new ArgumentNullException(nameof(obj)); } - List? temp = null; - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + obj.MyString = configuration["MyString"]!; + + if (configuration["MyInt"] is string value4) { - switch (section.Key) - { - case "MyString": - { - obj.MyString = configuration["MyString"]!; - } - break; - case "MyInt": - { - if (configuration["MyInt"] is string stringValue7) - { - obj.MyInt = ParseInt(stringValue7, () => section.Path)!; - } - } - break; - case "MyList": - { - if (HasChildren(section)) - { - List temp8 = obj.MyList; - temp8 ??= new List(); - BindCore(section, ref temp8, binderOptions); - obj.MyList = temp8; - } - } - break; - case "MyList2": - { - if (HasChildren(section)) - { - List temp9 = obj.MyList2; - temp9 ??= new List(); - BindCore(section, ref temp9, binderOptions); - obj.MyList2 = temp9; - } - } - break; - case "MyDictionary": - { - if (HasChildren(section)) - { - Dictionary temp10 = obj.MyDictionary; - temp10 ??= new Dictionary(); - BindCore(section, ref temp10, binderOptions); - obj.MyDictionary = temp10; - } - } - break; - default: - { - if (binderOptions?.ErrorOnUnknownConfiguration == true) - { - (temp ??= new List()).Add($"'{section.Key}'"); - } - } - break; - } + obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, binderOptions); + obj.MyList = temp7; } - if (temp is not null) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + List temp10 = obj.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, binderOptions); + obj.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary temp13 = obj.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, binderOptions); + obj.MyDictionary = temp13; + } + } + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } } } @@ -222,16 +193,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return true; } - return HasChildren(configuration); + return AsConfigWithChildren(configuration) is not null; } - public static bool HasChildren(IConfiguration configuration) + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) { - foreach (IConfigurationSection section in configuration.GetChildren()) + foreach (IConfigurationSection _ in configuration.GetChildren()) { - return true; + return configuration; } - return false; + return null; } public static BinderOptions? GetBinderOptions(Action? configureOptions) @@ -249,11 +220,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string stringValue, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs index f6bd0832385254..ed93f666b06410 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs @@ -12,13 +12,14 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests; using SourceGenerators.Tests; using Xunit; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests { [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] - public partial class ConfigurationBindingGeneratorTests + public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase { private static class Diagnostics { @@ -188,6 +189,59 @@ public class MyClass { } } } + [Fact] + public async Task BindCanParseMethodParam() + { + string source = """ + using System; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfiguration config = configurationBuilder.Build(); + + BindOptions(config, new MyClass0()); + BindOptions(config, new MyClass1(), (_) => { }); + BindOptions(config, "", new MyClass2()); + } + + private void BindOptions(IConfiguration config, MyClass0 instance) + { + config.Bind(instance); + } + + private void BindOptions(IConfiguration config, MyClass1 instance, Action? configureOptions) + { + config.Bind(instance, configureOptions); + } + + private void BindOptions(IConfiguration config, string path, MyClass2 instance) + { + config.Bind(path, instance); + } + + public class MyClass0 { } + public class MyClass1 { } + public class MyClass2 { } + } + """; + + var (d, r) = await RunGenerator(source); + Assert.Single(r); + + string generatedSource = string.Join('\n', r[0].SourceText.Lines.Select(x => x.ToString())); + Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass0 obj) => {{ }};", generatedSource); + Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass1 obj, global::System.Action? configureOptions) => {{ }};", generatedSource); + Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass2 obj) => {{ }};", generatedSource); + + Assert.Empty(d); + } + private static async Task VerifyAgainstBaselineUsingFile( string filename, string testSourceCode, @@ -203,12 +257,14 @@ private static async Task VerifyAgainstBaselineUsingFile( .Split(Environment.NewLine); var (d, r) = await RunGenerator(testSourceCode, languageVersion); + bool success = RoslynTestUtils.CompareLines(expectedLines, r[0].SourceText, + out string errorMessage); +#if !SKIP_BASELINES Assert.Single(r); (assessDiagnostics ?? ((d) => Assert.Empty(d))).Invoke(d); - - Assert.True(RoslynTestUtils.CompareLines(expectedLines, r[0].SourceText, - out string errorMessage), errorMessage); + Assert.True(success, errorMessage); +#endif } private static async Task<(ImmutableArray, ImmutableArray)> RunGenerator( diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj index e1390901fab897..b67b5ba6a753b0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj @@ -2,13 +2,18 @@ $(NetCoreAppCurrent);$(NetFrameworkMinimum) true - $(DefineConstants);BUILDING_SOURCE_GENERATOR_TESTS;ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER SYSLIB1100,SYSLIB1101,SYSLIB1103,SYSLIB1104 true + + $(DefineConstants);BUILDING_SOURCE_GENERATOR_TESTS;ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER + $(DefineConstants);LAUNCH_DEBUGGER + $(DefineConstants);SKIP_BASELINES + +