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 1721a124dead9..9182555af10fa 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs @@ -13,6 +13,9 @@ private sealed partial class Emitter private readonly InterceptorInfo _interceptorInfo; private readonly BindingHelperInfo _bindingHelperInfo; private readonly TypeIndex _typeIndex; + private readonly bool _emitEnumParseMethod; + private readonly bool _emitGenericParseEnum; + private readonly bool _emitThrowIfNullMethod; private readonly SourceWriter _writer = new(); @@ -21,6 +24,9 @@ public Emitter(SourceGenerationSpec sourceGenSpec) _interceptorInfo = sourceGenSpec.InterceptorInfo; _bindingHelperInfo = sourceGenSpec.BindingHelperInfo; _typeIndex = new TypeIndex(sourceGenSpec.ConfigTypes); + _emitEnumParseMethod = sourceGenSpec.EmitEnumParseMethod; + _emitGenericParseEnum = sourceGenSpec.EmitGenericParseEnum; + _emitThrowIfNullMethod = sourceGenSpec.EmitThrowIfNullMethod; } public void Emit(SourceProductionContext context) 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 3b5d460087751..eda240b098cc6 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -26,6 +26,8 @@ internal sealed partial class Parser(CompilationData compilationData) private readonly InterceptorInfo.Builder _interceptorInfoBuilder = new(); private BindingHelperInfo.Builder? _helperInfoBuilder; // Init'ed with type index when registering interceptors, after creating type specs. + private bool _emitEnumParseMethod; + private bool _emitGenericParseEnum; public List? Diagnostics { get; private set; } @@ -45,12 +47,16 @@ internal sealed partial class Parser(CompilationData compilationData) ParseInvocations(invocations); CreateTypeSpecs(cancellationToken); RegisterInterceptors(); + CheckIfToEmitParseEnumMethod(); return new SourceGenerationSpec { InterceptorInfo = _interceptorInfoBuilder.ToIncrementalValue(), BindingHelperInfo = _helperInfoBuilder!.ToIncrementalValue(), ConfigTypes = _createdTypeSpecs.Values.OrderBy(s => s.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(), + EmitEnumParseMethod = _emitEnumParseMethod, + EmitGenericParseEnum = _emitGenericParseEnum, + EmitThrowIfNullMethod = IsThrowIfNullMethodToBeEmitted() }; } @@ -840,6 +846,45 @@ private void RecordDiagnostic(DiagnosticDescriptor descriptor, Location trimmedL Diagnostics ??= new List(); Diagnostics.Add(DiagnosticInfo.Create(descriptor, trimmedLocation, messageArgs)); } + + private void CheckIfToEmitParseEnumMethod() + { + foreach (var typeSymbol in _createdTypeSpecs.Keys) + { + if (IsEnum(typeSymbol)) + { + _emitEnumParseMethod = true; + _emitGenericParseEnum = _typeSymbols.Enum.GetMembers("Parse").Any(m => m is IMethodSymbol methodSymbol && methodSymbol.IsGenericMethod); + return; + } + } + } + + private bool IsThrowIfNullMethodToBeEmitted() + { + if (_typeSymbols.ArgumentNullException is not null) + { + var throwIfNullMethods = _typeSymbols.ArgumentNullException.GetMembers("ThrowIfNull"); + + foreach (var throwIfNullMethod in throwIfNullMethods) + { + if (throwIfNullMethod is IMethodSymbol throwIfNullMethodSymbol && throwIfNullMethodSymbol.IsStatic && throwIfNullMethodSymbol.Parameters.Length == 2) + { + var parameters = throwIfNullMethodSymbol.Parameters; + var firstParam = parameters[0]; + var secondParam = parameters[1]; + + if (firstParam.Name == "argument" && firstParam.Type.SpecialType == SpecialType.System_Object + && secondParam.Name == "paramName" && secondParam.Type.Equals(_typeSymbols.String, SymbolEqualityComparer.Default)) + { + return true; + } + } + } + } + + return false; + } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs index 7d723139bde3e..4dbe36accbc6e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs @@ -144,8 +144,8 @@ void EmitMethods(ImmutableEquatableArray? interc Debug.Assert(!type.IsValueType); string binderOptionsArg = configureOptions ? $"{Identifier.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null"; - EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); - EmitCheckForNullArgument_WithBlankLine(Identifier.instance, voidReturn: true); + EmitCheckForNullArgument_WithBlankLine(Identifier.configuration, _emitThrowIfNullMethod); + EmitCheckForNullArgument_WithBlankLine(Identifier.instance, _emitThrowIfNullMethod, voidReturn: true); _writer.WriteLine($$""" var {{Identifier.typedObj}} = ({{type.DisplayString}}){{Identifier.instance}}; {{nameof(MethodsToGen_CoreBindingHelper.BindCore)}}({{configExpression}}, ref {{Identifier.typedObj}}, defaultValueIfNotFound: false, {{binderOptionsArg}}); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs index 499d4085bbd36..03366f8b49810 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -92,7 +92,7 @@ private void EmitGetCoreMethod() EmitBlankLineIfRequired(); EmitStartBlock($"public static object? {nameof(MethodsToGen_CoreBindingHelper.GetCore)}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, Action<{Identifier.BinderOptions}>? {Identifier.configureOptions})"); - EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); + EmitCheckForNullArgument_WithBlankLine(Identifier.configuration, _emitThrowIfNullMethod); _writer.WriteLine($"{Identifier.BinderOptions}? {Identifier.binderOptions} = {Identifier.GetBinderOptions}({Identifier.configureOptions});"); _writer.WriteLine(); @@ -178,7 +178,7 @@ private void EmitGetValueCoreMethod() EmitBlankLineIfRequired(); EmitStartBlock($"public static object? {nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key})"); - EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); + EmitCheckForNullArgument_WithBlankLine(Identifier.configuration, _emitThrowIfNullMethod); _writer.WriteLine($@"{Identifier.IConfigurationSection} {Identifier.section} = {GetSectionFromConfigurationExpression(Identifier.key, addQuotes: false)};"); _writer.WriteLine(); @@ -224,7 +224,7 @@ private void EmitBindCoreMainMethod() EmitBlankLineIfRequired(); EmitStartBlock($"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCoreMain)}({Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.instance}, Type {Identifier.type}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})"); - EmitCheckForNullArgument_WithBlankLine(Identifier.instance, voidReturn: true); + EmitCheckForNullArgument_WithBlankLine(Identifier.instance, _emitThrowIfNullMethod, voidReturn: true); EmitIConfigurationHasValueOrChildrenCheck(voidReturn: true); _writer.WriteLine($"{Identifier.BinderOptions}? {Identifier.binderOptions} = {Identifier.GetBinderOptions}({Identifier.configureOptions});"); _writer.WriteLine(); @@ -475,24 +475,20 @@ private void EmitHelperMethods() EmitGetBinderOptionsHelper(); } - if (_bindingHelperInfo.TypesForGen_ParsePrimitive is { Count: not 0 } stringParsableTypes) + if (_emitEnumParseMethod) { - bool enumTypeExists = false; + _writer.WriteLine(); + EmitEnumParseMethod(); + _emitBlankLineBeforeNextStatement = true; + } + if (_bindingHelperInfo.TypesForGen_ParsePrimitive is { Count: not 0 } stringParsableTypes) + { foreach (ParsableFromStringSpec type in stringParsableTypes) { - EmitBlankLineIfRequired(); - - if (type.StringParsableTypeKind == StringParsableTypeKind.Enum) - { - if (!enumTypeExists) - { - EmitEnumParseMethod(); - enumTypeExists = true; - } - } - else + if (type.StringParsableTypeKind is not StringParsableTypeKind.Enum) { + EmitBlankLineIfRequired(); EmitPrimitiveParseMethod(type); } } @@ -585,16 +581,13 @@ private void EmitEnumParseMethod() { string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof(T)}}"); + string parseEnumCall = _emitGenericParseEnum ? "Enum.Parse(value, ignoreCase: true)" : "(T)Enum.Parse(typeof(T), value, ignoreCase: true)"; _writer.WriteLine($$""" public static T ParseEnum(string value, Func getPath) where T : struct { try { - #if NETFRAMEWORK || NETSTANDARD2_0 - return (T)Enum.Parse(typeof(T), value, ignoreCase: true); - #else - return Enum.Parse(value, ignoreCase: true); - #endif + return {{parseEnumCall}}; } catch ({{Identifier.Exception}} {{Identifier.exception}}) { @@ -614,8 +607,6 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) switch (typeKind) { - case StringParsableTypeKind.Enum: - return; case StringParsableTypeKind.ByteArray: { parsedValueExpr = $"Convert.FromBase64String({Identifier.value})"; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs index 34a97d3c64c76..26c1f0c2c916d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs @@ -229,18 +229,30 @@ private void EmitBlankLineIfRequired() _emitBlankLineBeforeNextStatement = true; } - private void EmitCheckForNullArgument_WithBlankLine(string paramName, bool voidReturn = false) + private void EmitCheckForNullArgument_WithBlankLine(string paramName, bool useThrowIfNullMethod, bool voidReturn = false) { - string returnExpr = voidReturn - ? "return" - : $"throw new ArgumentNullException(nameof({paramName}))"; - - _writer.WriteLine($$""" + if (voidReturn) + { + _writer.WriteLine($$""" if ({{paramName}} is null) { - {{returnExpr}}; + return; } """); + } + else + { + string throwIfNullExpr = useThrowIfNullMethod + ? $"ArgumentNullException.ThrowIfNull({paramName});" + : $$""" + if ({{paramName}} is null) + { + throw new ArgumentNullException(nameof({{paramName}})); + } + """; + + _writer.WriteLine(throwIfNullExpr); + } _writer.WriteLine(); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsBuilderConfigurationExtensions.cs index fdc4286e34c55..14fab1e56f71f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsBuilderConfigurationExtensions.cs @@ -43,7 +43,7 @@ private void EmitBindMethods_Extensions_OptionsBuilder() paramList + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureBinder}", documentation); - EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder); + EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder, _emitThrowIfNullMethod); _writer.WriteLine($$""" {{Identifier.Configure}}<{{Identifier.TOptions}}>({{Identifier.optionsBuilder}}.{{Identifier.Services}}, {{Identifier.optionsBuilder}}.Name, {{Identifier.config}}, {{Identifier.configureBinder}}); @@ -65,11 +65,11 @@ private void EmitBindConfigurationMethod() EmitMethodStartBlock(MethodsToGen.OptionsBuilderExt_BindConfiguration, "BindConfiguration", paramList, documentation); - EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder); - EmitCheckForNullArgument_WithBlankLine(Identifier.configSectionPath); + EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder, _emitThrowIfNullMethod); + EmitCheckForNullArgument_WithBlankLine(Identifier.configSectionPath, _emitThrowIfNullMethod); EmitStartBlock($"{Identifier.optionsBuilder}.{Identifier.Configure}<{Identifier.IConfiguration}>(({Identifier.instance}, {Identifier.config}) =>"); - EmitCheckForNullArgument_WithBlankLine(Identifier.config); + EmitCheckForNullArgument_WithBlankLine(Identifier.config, _emitThrowIfNullMethod); _writer.WriteLine($$""" {{Identifier.IConfiguration}} {{Identifier.section}} = string.Equals(string.Empty, {{Identifier.configSectionPath}}, StringComparison.OrdinalIgnoreCase) ? {{Identifier.config}} : {{Identifier.config}}.{{Identifier.GetSection}}({{Identifier.configSectionPath}}); {{nameof(MethodsToGen_CoreBindingHelper.BindCoreMain)}}({{Identifier.section}}, {{Identifier.instance}}, typeof({{Identifier.TOptions}}), {{Identifier.configureBinder}}); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsConfigurationServiceCollectionExtensions.cs index daa3b79db8abc..63314facf5b72 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsConfigurationServiceCollectionExtensions.cs @@ -53,8 +53,8 @@ private void EmitConfigureMethods() // Like the others, it is public API that could be called directly by users. // So, it is always generated whenever a Configure overload is called. EmitStartMethod(MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions, paramList: $"string? {Identifier.name}, " + configParam + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}"); - EmitCheckForNullArgument_WithBlankLine(Identifier.services); - EmitCheckForNullArgument_WithBlankLine(Identifier.config); + EmitCheckForNullArgument_WithBlankLine(Identifier.services, _emitThrowIfNullMethod); + EmitCheckForNullArgument_WithBlankLine(Identifier.config, _emitThrowIfNullMethod); _writer.WriteLine($$""" OptionsServiceCollectionExtensions.AddOptions({{Identifier.services}}); {{Identifier.services}}.{{Identifier.AddSingleton}}<{{Identifier.IOptionsChangeTokenSource}}<{{Identifier.TOptions}}>>(new {{Identifier.ConfigurationChangeTokenSource}}<{{Identifier.TOptions}}>({{Identifier.name}}, {{Identifier.config}})); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs index 07dae8689782e..1b2b9d9d7f4a0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs @@ -56,6 +56,8 @@ internal sealed class KnownTypeSymbols public INamedTypeSymbol? ISet_Unbound { get; } public INamedTypeSymbol? ISet { get; } public INamedTypeSymbol? List { get; } + public INamedTypeSymbol Enum { get; } + public INamedTypeSymbol? ArgumentNullException { get; } public KnownTypeSymbols(CSharpCompilation compilation) { @@ -113,6 +115,12 @@ public KnownTypeSymbols(CSharpCompilation compilation) IReadOnlyList_Unbound = compilation.GetBestTypeByMetadataName(typeof(IReadOnlyList<>))?.ConstructUnboundGenericType(); IReadOnlySet_Unbound = compilation.GetBestTypeByMetadataName("System.Collections.Generic.IReadOnlySet`1")?.ConstructUnboundGenericType(); ISet_Unbound = ISet?.ConstructUnboundGenericType(); + + // needed to be able to know if a member exist inside the compilation unit + Enum = compilation.GetSpecialType(SpecialType.System_Enum); + ArgumentNullException = compilation.GetBestTypeByMetadataName(typeof(ArgumentNullException)); + + String = compilation.GetBestTypeByMetadataName(typeof(string)); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs index 4f57316429e2b..c4fc5a6079ad9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs @@ -10,5 +10,8 @@ public sealed record SourceGenerationSpec public required InterceptorInfo InterceptorInfo { get; init; } public required BindingHelperInfo BindingHelperInfo { get; init; } public required ImmutableEquatableArray ConfigTypes { get; init; } + public required bool EmitEnumParseMethod { get; set; } + public required bool EmitGenericParseEnum { get; set; } + public required bool EmitThrowIfNullMethod { get; set; } } } 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/net462/Collections.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Collections.generated.txt 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/net462/ConfigurationBinder/Bind.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind.generated.txt 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/net462/ConfigurationBinder/Bind_Instance.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_Instance.generated.txt 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/net462/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt 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/net462/ConfigurationBinder/Bind_Key_Instance.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_Key_Instance.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt 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/net462/ConfigurationBinder/Get.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get.generated.txt 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/net462/ConfigurationBinder/GetValue.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue.generated.txt 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/net462/ConfigurationBinder/GetValue_T_Key.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_T_Key.generated.txt 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/net462/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt 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/net462/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt 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/net462/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_PrimitivesOnly.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_PrimitivesOnly.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_PrimitivesOnly.generated.txt 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/net462/ConfigurationBinder/Get_T.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_T.generated.txt 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/net462/ConfigurationBinder/Get_T_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_T_BinderOptions.generated.txt 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/net462/ConfigurationBinder/Get_TypeOf.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_TypeOf.generated.txt 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/net462/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/EmptyConfigType.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/EmptyConfigType.generated.txt 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/net462/OptionsBuilder/BindConfiguration.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/BindConfiguration.generated.txt 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/net462/OptionsBuilder/Bind_T.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Bind_T.generated.txt 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/net462/OptionsBuilder/Bind_T_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Bind_T_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Primitives.generated.txt new file mode 100644 index 0000000000000..6f189bf2e55ba --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Primitives.generated.txt @@ -0,0 +1,558 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 13, 16)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? instance) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (instance is null) + { + return; + } + + var typedObj = (Program.MyClass)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + 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", "Prop28", "Prop29", "Prop30" }); + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["Prop0"] is string value0) + { + instance.Prop0 = ParseBool(value0, () => configuration.GetSection("Prop0").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop0 = default; + } + + if (configuration["Prop1"] is string value1) + { + instance.Prop1 = ParseByte(value1, () => configuration.GetSection("Prop1").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop1 = default; + } + + if (configuration["Prop2"] is string value2) + { + instance.Prop2 = ParseSbyte(value2, () => configuration.GetSection("Prop2").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop2 = default; + } + + if (configuration["Prop3"] is string value3) + { + instance.Prop3 = ParseChar(value3, () => configuration.GetSection("Prop3").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop3 = default; + } + + if (configuration["Prop4"] is string value4) + { + instance.Prop4 = ParseDouble(value4, () => configuration.GetSection("Prop4").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop4 = default; + } + + if (configuration["Prop5"] is string value5) + { + instance.Prop5 = value5; + } + + if (configuration["Prop6"] is string value6) + { + instance.Prop6 = ParseInt(value6, () => configuration.GetSection("Prop6").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop6 = default; + } + + if (configuration["Prop8"] is string value7) + { + instance.Prop8 = ParseShort(value7, () => configuration.GetSection("Prop8").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop8 = default; + } + + if (configuration["Prop9"] is string value8) + { + instance.Prop9 = ParseLong(value8, () => configuration.GetSection("Prop9").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop9 = default; + } + + if (configuration["Prop10"] is string value9) + { + instance.Prop10 = ParseFloat(value9, () => configuration.GetSection("Prop10").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop10 = default; + } + + if (configuration["Prop13"] is string value10) + { + instance.Prop13 = ParseUshort(value10, () => configuration.GetSection("Prop13").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop13 = default; + } + + if (configuration["Prop14"] is string value11) + { + instance.Prop14 = ParseUint(value11, () => configuration.GetSection("Prop14").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop14 = default; + } + + if (configuration["Prop15"] is string value12) + { + instance.Prop15 = ParseUlong(value12, () => configuration.GetSection("Prop15").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop15 = default; + } + + if (configuration["Prop16"] is string value13) + { + instance.Prop16 = value13; + } + + if (configuration["Prop17"] is string value14) + { + instance.Prop17 = ParseCultureInfo(value14, () => configuration.GetSection("Prop17").Path); + } + + if (configuration["Prop19"] is string value15) + { + instance.Prop19 = ParseDateTime(value15, () => configuration.GetSection("Prop19").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop19 = default; + } + + if (configuration["Prop20"] is string value16) + { + instance.Prop20 = ParseDateTimeOffset(value16, () => configuration.GetSection("Prop20").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop20 = default; + } + + if (configuration["Prop21"] is string value17) + { + instance.Prop21 = ParseDecimal(value17, () => configuration.GetSection("Prop21").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop21 = default; + } + + if (configuration["Prop23"] is string value18) + { + instance.Prop23 = ParseTimeSpan(value18, () => configuration.GetSection("Prop23").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop23 = default; + } + + if (configuration["Prop24"] is string value19) + { + instance.Prop24 = ParseGuid(value19, () => configuration.GetSection("Prop24").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop24 = default; + } + + if (configuration["Prop25"] is string value20) + { + instance.Prop25 = ParseUri(value20, () => configuration.GetSection("Prop25").Path); + } + + if (configuration["Prop26"] is string value21) + { + instance.Prop26 = ParseVersion(value21, () => configuration.GetSection("Prop26").Path); + } + + if (configuration["Prop27"] is string value22) + { + instance.Prop27 = ParseEnum(value22, () => configuration.GetSection("Prop27").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop27 = default; + } + + if (configuration["Prop28"] is string value23) + { + instance.Prop28 = ParseByteArray(value23, () => configuration.GetSection("Prop28").Path); + } + + if (configuration["Prop29"] is string value24) + { + instance.Prop29 = ParseInt(value24, () => configuration.GetSection("Prop29").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop29 = default; + } + + if (configuration["Prop30"] is string value25) + { + instance.Prop30 = ParseDateTime(value25, () => configuration.GetSection("Prop30").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop30 = default; + } + } + + + /// 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 T ParseEnum(string value, Func getPath) where T : struct + { + try + { + return (T)Enum.Parse(typeof(T), value, ignoreCase: true); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(T)}'.", exception); + } + } + + public static bool ParseBool(string value, Func getPath) + { + try + { + return bool.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); + } + } + + public static byte ParseByte(string value, Func getPath) + { + try + { + return byte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte)}'.", exception); + } + } + + public static sbyte ParseSbyte(string value, Func getPath) + { + try + { + return sbyte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(sbyte)}'.", exception); + } + } + + public static char ParseChar(string value, Func getPath) + { + try + { + return char.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(char)}'.", exception); + } + } + + public static double ParseDouble(string value, Func getPath) + { + try + { + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(double)}'.", exception); + } + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + + public static short ParseShort(string value, Func getPath) + { + try + { + return short.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(short)}'.", exception); + } + } + + public static long ParseLong(string value, Func getPath) + { + try + { + return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(long)}'.", exception); + } + } + + public static float ParseFloat(string value, Func getPath) + { + try + { + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(float)}'.", exception); + } + } + + public static ushort ParseUshort(string value, Func getPath) + { + try + { + return ushort.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ushort)}'.", exception); + } + } + + public static uint ParseUint(string value, Func getPath) + { + try + { + return uint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(uint)}'.", exception); + } + } + + public static ulong ParseUlong(string value, Func getPath) + { + try + { + return ulong.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ulong)}'.", exception); + } + } + + public static CultureInfo ParseCultureInfo(string value, Func getPath) + { + try + { + return CultureInfo.GetCultureInfo(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); + } + } + + public static DateTime ParseDateTime(string value, Func getPath) + { + try + { + return DateTime.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTime)}'.", exception); + } + } + + public static DateTimeOffset ParseDateTimeOffset(string value, Func getPath) + { + try + { + return DateTimeOffset.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTimeOffset)}'.", exception); + } + } + + public static decimal ParseDecimal(string value, Func getPath) + { + try + { + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(decimal)}'.", exception); + } + } + + public static TimeSpan ParseTimeSpan(string value, Func getPath) + { + try + { + return TimeSpan.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(TimeSpan)}'.", exception); + } + } + + public static Guid ParseGuid(string value, Func getPath) + { + try + { + return Guid.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Guid)}'.", exception); + } + } + + public static Uri ParseUri(string value, Func getPath) + { + try + { + return new Uri(value, UriKind.RelativeOrAbsolute); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Uri)}'.", exception); + } + } + + public static Version ParseVersion(string value, Func getPath) + { + try + { + return Version.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Version)}'.", exception); + } + } + + public static byte[] ParseByteArray(string value, Func getPath) + { + try + { + return Convert.FromBase64String(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); + } + } + #endregion Core binding extensions. + } +} 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/net462/ServiceCollection/Configure_T.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T.generated.txt 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/net462/ServiceCollection/Configure_T_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T_BinderOptions.generated.txt 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/net462/ServiceCollection/Configure_T_name.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T_name.generated.txt 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/net462/ServiceCollection/Configure_T_name_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T_name_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Collections.generated.txt new file mode 100644 index 0000000000000..b0210df5e044e --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Collections.generated.txt @@ -0,0 +1,226 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 12, 17)] + public static T? Get(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClassWithCustomCollections = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "CustomDictionary", "CustomList", "ICustomDictionary", "ICustomCollection", "IReadOnlyList", "UnsupportedIReadOnlyDictionaryUnsupported", "IReadOnlyDictionary" }); + + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + ArgumentNullException.ThrowIfNull(configuration); + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClassWithCustomCollections)) + { + var instance = new Program.MyClassWithCustomCollections(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref Program.CustomDictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = ParseInt(value, () => section.Path); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.CustomList instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(value); + } + } + } + + public static void BindCore(IConfiguration configuration, ref IReadOnlyList instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + if (instance is not ICollection temp) + { + return; + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + temp.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref IReadOnlyDictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + if (instance is not IDictionary temp) + { + return; + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + temp[section.Key] = ParseInt(value, () => section.Path); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClassWithCustomCollections instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1) + { + Program.CustomDictionary? temp3 = instance.CustomDictionary; + temp3 ??= new Program.CustomDictionary(); + BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); + instance.CustomDictionary = temp3; + } + + if (AsConfigWithChildren(configuration.GetSection("CustomList")) is IConfigurationSection section4) + { + Program.CustomList? temp6 = instance.CustomList; + temp6 ??= new Program.CustomList(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.CustomList = temp6; + } + + if (AsConfigWithChildren(configuration.GetSection("IReadOnlyList")) is IConfigurationSection section7) + { + IReadOnlyList? temp9 = instance.IReadOnlyList; + temp9 = temp9 is null ? (IReadOnlyList)new List() : (IReadOnlyList)new List(temp9); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.IReadOnlyList = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("IReadOnlyDictionary")) is IConfigurationSection section10) + { + IReadOnlyDictionary? temp12 = instance.IReadOnlyDictionary; + temp12 = temp12 is null ? (IReadOnlyDictionary)new Dictionary() : (IReadOnlyDictionary)temp12.ToDictionary(pair => pair.Key, pair => pair.Value); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.IReadOnlyDictionary = temp12; + } + } + + + /// 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 HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind.generated.txt new file mode 100644 index 0000000000000..c1c98b7d1e162 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind.generated.txt @@ -0,0 +1,222 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 12, 14)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? instance) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (Program.MyClass)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 13, 20)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? instance, Action? configureOptions) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (Program.MyClass)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, GetBinderOptions(configureOptions)); + } + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 14, 20)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, string key, object? instance) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (Program.MyClass)instance; + BindCore(configuration.GetSection(key), ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp10; + } + } + + + /// 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 IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Instance.generated.txt new file mode 100644 index 0000000000000..8c961fff97813 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Instance.generated.txt @@ -0,0 +1,174 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 12, 20)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? instance) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (Program.MyClass)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp10; + } + } + + + /// 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 IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt new file mode 100644 index 0000000000000..f7ea54e511a57 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt @@ -0,0 +1,192 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 12, 20)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? instance, Action? configureOptions) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (Program.MyClass)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, GetBinderOptions(configureOptions)); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp10; + } + } + + + /// 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 IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Key_Instance.generated.txt new file mode 100644 index 0000000000000..22e430f70e6b7 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Key_Instance.generated.txt @@ -0,0 +1,174 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 12, 20)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, string key, object? instance) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (Program.MyClass)instance; + BindCore(configuration.GetSection(key), ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp10; + } + } + + + /// 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 IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt new file mode 100644 index 0000000000000..14753a4a1f8e4 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt @@ -0,0 +1,72 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 18, 16)] + public static void Bind_ProgramMyClass0(this IConfiguration configuration, object? instance) + { + } + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 23, 16)] + public static void Bind_ProgramMyClass1(this IConfiguration configuration, object? instance, Action? configureOptions) + { + } + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 28, 16)] + public static void Bind_ProgramMyClass2(this IConfiguration configuration, string key, object? instance) + { + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get.generated.txt new file mode 100644 index 0000000000000..a050ddffd430f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get.generated.txt @@ -0,0 +1,249 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 12, 38)] + public static T? Get(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); + + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 14, 36)] + public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 13, 56)] + public static object? Get(this IConfiguration configuration, Type type) => GetCore(configuration, type, configureOptions: null); + + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 15, 47)] + public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + #endregion IConfiguration extensions. + + #region Core binding extensions. + 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) + { + ArgumentNullException.ThrowIfNull(configuration); + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClass)) + { + var instance = new Program.MyClass(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + else if (type == typeof(Program.MyClass2)) + { + var instance = new Program.MyClass2(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + var temp2 = new List(); + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + temp2.Add(ParseInt(value, () => section.Path)); + } + } + + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp2.Count); + temp2.CopyTo(instance, originalCount); + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8) + { + int[]? temp10 = instance.MyArray; + temp10 ??= new int[0]; + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp13; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value14) + { + instance.MyInt = ParseInt(value14, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + } + + + /// 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 HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue.generated.txt new file mode 100644 index 0000000000000..cd7bd92c05b8d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue.generated.txt @@ -0,0 +1,130 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.CodeDom.Compiler; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocation(@"src-0.cs", 13, 18)] + public static T? GetValue(this IConfiguration configuration, string key) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? default(T)); + + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocation(@"src-0.cs", 16, 24)] + public static T? GetValue(this IConfiguration configuration, string key, T defaultValue) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? defaultValue); + + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocation(@"src-0.cs", 14, 24)] + public static object? GetValue(this IConfiguration configuration, Type type, string key) => BindingExtensions.GetValueCore(configuration, type, key); + + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocation(@"src-0.cs", 17, 24)] + public static object? GetValue(this IConfiguration configuration, Type type, string key, object? defaultValue) => BindingExtensions.GetValueCore(configuration, type, key) ?? defaultValue; + #endregion IConfiguration extensions. + + #region Core binding extensions. + public static object? GetValueCore(this IConfiguration configuration, Type type, string key) + { + ArgumentNullException.ThrowIfNull(configuration); + + IConfigurationSection section = configuration.GetSection(key); + + if (section.Value is not string value) + { + return null; + } + + if (type == typeof(int)) + { + return ParseInt(value, () => section.Path); + } + else if (type == typeof(bool?)) + { + return ParseBool(value, () => section.Path); + } + else if (type == typeof(byte[])) + { + return ParseByteArray(value, () => section.Path); + } + else if (type == typeof(CultureInfo)) + { + return ParseCultureInfo(value, () => section.Path); + } + + return null; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + + public static bool ParseBool(string value, Func getPath) + { + try + { + return bool.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); + } + } + + public static byte[] ParseByteArray(string value, Func getPath) + { + try + { + return Convert.FromBase64String(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); + } + } + + public static CultureInfo ParseCultureInfo(string value, Func getPath) + { + try + { + return CultureInfo.GetCultureInfo(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_T_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_T_Key.generated.txt new file mode 100644 index 0000000000000..1ebddd4bdc3ac --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_T_Key.generated.txt @@ -0,0 +1,70 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.CodeDom.Compiler; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocation(@"src-0.cs", 10, 20)] + public static T? GetValue(this IConfiguration configuration, string key) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? default(T)); + #endregion IConfiguration extensions. + + #region Core binding extensions. + public static object? GetValueCore(this IConfiguration configuration, Type type, string key) + { + ArgumentNullException.ThrowIfNull(configuration); + + IConfigurationSection section = configuration.GetSection(key); + + if (section.Value is not string value) + { + return null; + } + + if (type == typeof(int)) + { + return ParseInt(value, () => section.Path); + } + + return null; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt new file mode 100644 index 0000000000000..897a937ea31f6 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt @@ -0,0 +1,70 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.CodeDom.Compiler; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocation(@"src-0.cs", 12, 20)] + public static T? GetValue(this IConfiguration configuration, string key, T defaultValue) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? defaultValue); + #endregion IConfiguration extensions. + + #region Core binding extensions. + public static object? GetValueCore(this IConfiguration configuration, Type type, string key) + { + ArgumentNullException.ThrowIfNull(configuration); + + IConfigurationSection section = configuration.GetSection(key); + + if (section.Value is not string value) + { + return null; + } + + if (type == typeof(int)) + { + return ParseInt(value, () => section.Path); + } + + return null; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt new file mode 100644 index 0000000000000..8fd4c52ad6c93 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt @@ -0,0 +1,70 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.CodeDom.Compiler; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocation(@"src-0.cs", 10, 20)] + public static object? GetValue(this IConfiguration configuration, Type type, string key) => BindingExtensions.GetValueCore(configuration, type, key); + #endregion IConfiguration extensions. + + #region Core binding extensions. + public static object? GetValueCore(this IConfiguration configuration, Type type, string key) + { + ArgumentNullException.ThrowIfNull(configuration); + + IConfigurationSection section = configuration.GetSection(key); + + if (section.Value is not string value) + { + return null; + } + + if (type == typeof(bool?)) + { + return ParseBool(value, () => section.Path); + } + + return null; + } + + public static bool ParseBool(string value, Func getPath) + { + try + { + return bool.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt new file mode 100644 index 0000000000000..e500077432612 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt @@ -0,0 +1,70 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.CodeDom.Compiler; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocation(@"src-0.cs", 11, 20)] + public static object? GetValue(this IConfiguration configuration, Type type, string key, object? defaultValue) => BindingExtensions.GetValueCore(configuration, type, key) ?? defaultValue; + #endregion IConfiguration extensions. + + #region Core binding extensions. + public static object? GetValueCore(this IConfiguration configuration, Type type, string key) + { + ArgumentNullException.ThrowIfNull(configuration); + + IConfigurationSection section = configuration.GetSection(key); + + if (section.Value is not string value) + { + return null; + } + + if (type == typeof(CultureInfo)) + { + return ParseCultureInfo(value, () => section.Path); + } + + return null; + } + + public static CultureInfo ParseCultureInfo(string value, Func getPath) + { + try + { + return CultureInfo.GetCultureInfo(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_PrimitivesOnly.generated.txt new file mode 100644 index 0000000000000..021a4ca8bef4e --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_PrimitivesOnly.generated.txt @@ -0,0 +1,179 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.CodeDom.Compiler; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 10, 16)] + public static T? Get(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); + + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 12, 16)] + public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 11, 16)] + public static object? Get(this IConfiguration configuration, Type type) => GetCore(configuration, type, configureOptions: null); + + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 13, 16)] + public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + #endregion IConfiguration extensions. + + #region Core binding extensions. + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + ArgumentNullException.ThrowIfNull(configuration); + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(int)) + { + if (configuration is not IConfigurationSection section) + { + throw new InvalidOperationException(); + } + if (section.Value is string value) + { + return ParseInt(value, () => section.Path); + } + } + else if (type == typeof(string)) + { + if (configuration is not IConfigurationSection section) + { + throw new InvalidOperationException(); + } + return section.Value; + } + else if (type == typeof(float)) + { + if (configuration is not IConfigurationSection section) + { + throw new InvalidOperationException(); + } + if (section.Value is string value) + { + return ParseFloat(value, () => section.Path); + } + } + else if (type == typeof(double)) + { + if (configuration is not IConfigurationSection section) + { + throw new InvalidOperationException(); + } + if (section.Value is string value) + { + return ParseDouble(value, () => section.Path); + } + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + + public static float ParseFloat(string value, Func getPath) + { + try + { + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(float)}'.", exception); + } + } + + public static double ParseDouble(string value, Func getPath) + { + try + { + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(double)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_T.generated.txt new file mode 100644 index 0000000000000..c054010f560a0 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_T.generated.txt @@ -0,0 +1,216 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 11, 40)] + public static T? Get(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); + #endregion IConfiguration extensions. + + #region Core binding extensions. + 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) + { + ArgumentNullException.ThrowIfNull(configuration); + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClass)) + { + var instance = new Program.MyClass(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + var temp1 = new List(); + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + temp1.Add(ParseInt(value, () => section.Path)); + } + } + + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp1.Count); + temp1.CopyTo(instance, originalCount); + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value2) + { + instance.MyString = value2; + } + + if (configuration["MyInt"] is string value3) + { + instance.MyInt = ParseInt(value3, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + { + List? temp6 = instance.MyList; + temp6 ??= new List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; + } + + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section7) + { + int[]? temp9 = instance.MyArray; + temp9 ??= new int[0]; + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section10) + { + Dictionary? temp12 = instance.MyDictionary; + temp12 ??= new Dictionary(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp12; + } + } + + + /// 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 HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_T_BinderOptions.generated.txt new file mode 100644 index 0000000000000..9266520e105a6 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_T_BinderOptions.generated.txt @@ -0,0 +1,216 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 11, 40)] + public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + #endregion IConfiguration extensions. + + #region Core binding extensions. + 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) + { + ArgumentNullException.ThrowIfNull(configuration); + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClass)) + { + var instance = new Program.MyClass(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + var temp1 = new List(); + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + temp1.Add(ParseInt(value, () => section.Path)); + } + } + + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp1.Count); + temp1.CopyTo(instance, originalCount); + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value2) + { + instance.MyString = value2; + } + + if (configuration["MyInt"] is string value3) + { + instance.MyInt = ParseInt(value3, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + { + List? temp6 = instance.MyList; + temp6 ??= new List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; + } + + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section7) + { + int[]? temp9 = instance.MyArray; + temp9 ??= new int[0]; + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section10) + { + Dictionary? temp12 = instance.MyDictionary; + temp12 ??= new Dictionary(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp12; + } + } + + + /// 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 HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_TypeOf.generated.txt new file mode 100644 index 0000000000000..8818894f387c4 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_TypeOf.generated.txt @@ -0,0 +1,148 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 11, 51)] + public static object? Get(this IConfiguration configuration, Type type) => GetCore(configuration, type, configureOptions: null); + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); + + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + ArgumentNullException.ThrowIfNull(configuration); + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClass2)) + { + var instance = new Program.MyClass2(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + } + + + /// 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 HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt new file mode 100644 index 0000000000000..6202545f7ae8d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt @@ -0,0 +1,148 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 11, 20)] + public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); + + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + ArgumentNullException.ThrowIfNull(configuration); + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClass2)) + { + var instance = new Program.MyClass2(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + } + + + /// 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 HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/EmptyConfigType.generated.txt new file mode 100644 index 0000000000000..0bb9d336b81e9 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/EmptyConfigType.generated.txt @@ -0,0 +1,101 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +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.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 12, 23)] + public static void Bind_TypeWithNoMembers(this IConfiguration configuration, object? instance) + { + } + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 15, 23)] + public static void Bind_TypeWithNoMembers_Wrapper(this IConfiguration configuration, object? instance) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (TypeWithNoMembers_Wrapper)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_TypeWithNoMembers_Wrapper = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Member" }); + + public static void BindCore(IConfiguration configuration, ref TypeWithNoMembers_Wrapper instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(TypeWithNoMembers_Wrapper), s_configKeys_TypeWithNoMembers_Wrapper, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("Member")) is IConfigurationSection section0) + { + instance.Member ??= new TypeWithNoMembers(); + } + } + + + /// 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 IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/BindConfiguration.generated.txt new file mode 100644 index 0000000000000..682eacc221dde --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/BindConfiguration.generated.txt @@ -0,0 +1,193 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using System; + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region OptionsBuilder extensions. + /// Registers the dependency injection container to bind against the obtained from the DI service provider. + [InterceptsLocation(@"src-0.cs", 12, 24)] + public static OptionsBuilder BindConfiguration(this OptionsBuilder optionsBuilder, string configSectionPath, Action? configureBinder = null) where TOptions : class + { + ArgumentNullException.ThrowIfNull(optionsBuilder); + + ArgumentNullException.ThrowIfNull(configSectionPath); + + optionsBuilder.Configure((instance, config) => + { + ArgumentNullException.ThrowIfNull(config); + + IConfiguration section = string.Equals(string.Empty, configSectionPath, StringComparison.OrdinalIgnoreCase) ? config : config.GetSection(configSectionPath); + BindCoreMain(section, instance, typeof(TOptions), configureBinder); + }); + + optionsBuilder.Services.AddSingleton, ConfigurationChangeTokenSource>(); + return optionsBuilder; + } + #endregion OptionsBuilder extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); + + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + { + if (instance is null) + { + return; + } + + if (!HasValueOrChildren(configuration)) + { + return; + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (type == typeof(Program.MyClass)) + { + var temp = (Program.MyClass)instance; + BindCore(configuration, ref temp, defaultValueIfNotFound: false, binderOptions); + return; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + List? temp5 = instance.MyList; + temp5 ??= new List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.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)}"); + } + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Bind_T.generated.txt new file mode 100644 index 0000000000000..76023bb8f04e2 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Bind_T.generated.txt @@ -0,0 +1,203 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using System; + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region OptionsBuilder extensions. + /// Registers a configuration instance which will bind against. + [InterceptsLocation(@"src-0.cs", 15, 24)] + public static OptionsBuilder Bind(this OptionsBuilder optionsBuilder, IConfiguration config) where TOptions : class + { + return Bind(optionsBuilder, config, configureBinder: null); + } + + /// Registers a configuration instance which will bind against. + public static OptionsBuilder Bind(this OptionsBuilder optionsBuilder, IConfiguration config, Action? configureBinder) where TOptions : class + { + ArgumentNullException.ThrowIfNull(optionsBuilder); + + Configure(optionsBuilder.Services, optionsBuilder.Name, config, configureBinder); + return optionsBuilder; + } + #endregion OptionsBuilder extensions. + + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + { + ArgumentNullException.ThrowIfNull(services); + + ArgumentNullException.ThrowIfNull(config); + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, config)); + return services.AddSingleton>(new ConfigureNamedOptions(name, instance => BindCoreMain(config, instance, typeof(TOptions), configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); + + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + { + if (instance is null) + { + return; + } + + if (!HasValueOrChildren(configuration)) + { + return; + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (type == typeof(Program.MyClass)) + { + var temp = (Program.MyClass)instance; + BindCore(configuration, ref temp, defaultValueIfNotFound: false, binderOptions); + return; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + List? temp5 = instance.MyList; + temp5 ??= new List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.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)}"); + } + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Bind_T_BinderOptions.generated.txt new file mode 100644 index 0000000000000..c6336da8dfaec --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Bind_T_BinderOptions.generated.txt @@ -0,0 +1,197 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using System; + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region OptionsBuilder extensions. + /// Registers a configuration instance which will bind against. + [InterceptsLocation(@"src-0.cs", 15, 24)] + public static OptionsBuilder Bind(this OptionsBuilder optionsBuilder, IConfiguration config, Action? configureBinder) where TOptions : class + { + ArgumentNullException.ThrowIfNull(optionsBuilder); + + Configure(optionsBuilder.Services, optionsBuilder.Name, config, configureBinder); + return optionsBuilder; + } + #endregion OptionsBuilder extensions. + + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + { + ArgumentNullException.ThrowIfNull(services); + + ArgumentNullException.ThrowIfNull(config); + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, config)); + return services.AddSingleton>(new ConfigureNamedOptions(name, instance => BindCoreMain(config, instance, typeof(TOptions), configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); + + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + { + if (instance is null) + { + return; + } + + if (!HasValueOrChildren(configuration)) + { + return; + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (type == typeof(Program.MyClass)) + { + var temp = (Program.MyClass)instance; + BindCore(configuration, ref temp, defaultValueIfNotFound: false, binderOptions); + return; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + List? temp5 = instance.MyList; + temp5 ??= new List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.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)}"); + } + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} 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/netcoreapp/Primitives.generated.txt similarity index 98% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Primitives.generated.txt index a8373ca527095..2d42875b9f996 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Primitives.generated.txt @@ -34,10 +34,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration [InterceptsLocation(@"src-0.cs", 13, 16)] public static void Bind_ProgramMyClass(this IConfiguration configuration, object? instance) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + ArgumentNullException.ThrowIfNull(configuration); if (instance is null) { @@ -335,6 +332,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static T ParseEnum(string value, Func getPath) where T : struct + { + try + { + return Enum.Parse(value, ignoreCase: true); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(T)}'.", exception); + } + } + public static bool ParseBool(string value, Func getPath) { try @@ -575,22 +584,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static T ParseEnum(string value, Func getPath) where T : struct - { - try - { - #if NETFRAMEWORK || NETSTANDARD2_0 - return (T)Enum.Parse(typeof(T), value, ignoreCase: true); - #else - return Enum.Parse(value, ignoreCase: true); - #endif - } - catch (Exception exception) - { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(T)}'.", exception); - } - } - public static Int128 ParseInt128(string value, Func getPath) { try diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T.generated.txt new file mode 100644 index 0000000000000..5d5b84cc1765a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T.generated.txt @@ -0,0 +1,244 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using System; + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + [InterceptsLocation(@"src-0.cs", 14, 18)] + public static IServiceCollection Configure(this IServiceCollection services, IConfiguration config) where TOptions : class + { + return Configure(services, string.Empty, config, configureOptions: null); + } + + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + { + ArgumentNullException.ThrowIfNull(services); + + ArgumentNullException.ThrowIfNull(config); + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, config)); + return services.AddSingleton>(new ConfigureNamedOptions(name, instance => BindCoreMain(config, instance, typeof(TOptions), configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. + 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + { + if (instance is null) + { + return; + } + + if (!HasValueOrChildren(configuration)) + { + return; + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (type == typeof(Program.MyClass)) + { + var temp = (Program.MyClass)instance; + BindCore(configuration, ref temp, defaultValueIfNotFound: false, binderOptions); + return; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.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)}"); + } + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_BinderOptions.generated.txt new file mode 100644 index 0000000000000..9c75578aef9f7 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_BinderOptions.generated.txt @@ -0,0 +1,244 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using System; + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + [InterceptsLocation(@"src-0.cs", 14, 18)] + public static IServiceCollection Configure(this IServiceCollection services, IConfiguration config, Action? configureOptions) where TOptions : class + { + return Configure(services, string.Empty, config, configureOptions); + } + + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + { + ArgumentNullException.ThrowIfNull(services); + + ArgumentNullException.ThrowIfNull(config); + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, config)); + return services.AddSingleton>(new ConfigureNamedOptions(name, instance => BindCoreMain(config, instance, typeof(TOptions), configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. + 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + { + if (instance is null) + { + return; + } + + if (!HasValueOrChildren(configuration)) + { + return; + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (type == typeof(Program.MyClass)) + { + var temp = (Program.MyClass)instance; + BindCore(configuration, ref temp, defaultValueIfNotFound: false, binderOptions); + return; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.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)}"); + } + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_name.generated.txt new file mode 100644 index 0000000000000..6d396b26bdefd --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_name.generated.txt @@ -0,0 +1,244 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using System; + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + [InterceptsLocation(@"src-0.cs", 14, 18)] + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config) where TOptions : class + { + return Configure(services, name, config, configureOptions: null); + } + + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + { + ArgumentNullException.ThrowIfNull(services); + + ArgumentNullException.ThrowIfNull(config); + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, config)); + return services.AddSingleton>(new ConfigureNamedOptions(name, instance => BindCoreMain(config, instance, typeof(TOptions), configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. + 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + { + if (instance is null) + { + return; + } + + if (!HasValueOrChildren(configuration)) + { + return; + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (type == typeof(Program.MyClass)) + { + var temp = (Program.MyClass)instance; + BindCore(configuration, ref temp, defaultValueIfNotFound: false, binderOptions); + return; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.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)}"); + } + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_name_BinderOptions.generated.txt new file mode 100644 index 0000000000000..2834491e35135 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_name_BinderOptions.generated.txt @@ -0,0 +1,238 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using System; + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + [InterceptsLocation(@"src-0.cs", 14, 18)] + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + { + ArgumentNullException.ThrowIfNull(services); + + ArgumentNullException.ThrowIfNull(config); + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, config)); + return services.AddSingleton>(new ConfigureNamedOptions(name, instance => BindCoreMain(config, instance, typeof(TOptions), configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. + 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + { + if (instance is null) + { + return; + } + + if (!HasValueOrChildren(configuration)) + { + return; + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (type == typeof(Program.MyClass)) + { + var temp = (Program.MyClass)instance; + BindCore(configuration, ref temp, defaultValueIfNotFound: false, binderOptions); + return; + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.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)}"); + } + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs index 4373b404fc67f..d4d207fff3c6b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs @@ -96,18 +96,18 @@ private async Task UpdateCompilationWithSource(string? source = null) internal struct ConfigBindingGenRunResult { - public required Compilation OutputCompilation { get; init; } + public Compilation OutputCompilation { get; init; } - public required GeneratedSourceResult? GeneratedSource { get; init; } + public GeneratedSourceResult? GeneratedSource { get; init; } /// /// Diagnostics produced by the generator alone. Doesn't include any from other build participants. /// - public required ImmutableArray Diagnostics { get; init; } + public ImmutableArray Diagnostics { get; init; } - public required ImmutableArray TrackedSteps { get; init; } + public ImmutableArray TrackedSteps { get; init; } - public required SourceGenerationSpec? GenerationSpec { get; init; } + public SourceGenerationSpec? GenerationSpec { get; init; } } internal enum ExpectedDiagnostics diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs index e05a773713712..73d43a5c3ab10 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs @@ -684,7 +684,7 @@ public class MyClass2 Assert.Empty(result.Diagnostics); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] public async Task Primitives() { string source = """ @@ -739,6 +739,59 @@ public class MyClass } } """; + await VerifyAgainstBaselineUsingFile("Primitives.generated.txt", source); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetFramework))] + public async Task PrimitivesNetFwk() + { + string source = """ + using System; + using System.Globalization; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass obj = new(); + config.Bind(obj); + } + + public class MyClass + { + public bool Prop0 { get; set; } + public byte Prop1 { get; set; } + public sbyte Prop2 { get; set; } + public char Prop3 { get; set; } + public double Prop4 { get; set; } + public string Prop5 { get; set; } + public int Prop6 { get; set; } + public short Prop8 { get; set; } + public long Prop9 { get; set; } + public float Prop10 { get; set; } + public ushort Prop13 { get; set; } + public uint Prop14 { get; set; } + public ulong Prop15 { get; set; } + public object Prop16 { get; set; } + public CultureInfo Prop17 { get; set; } + public DateTime Prop19 { get; set; } + public DateTimeOffset Prop20 { get; set; } + public decimal Prop21 { get; set; } + public TimeSpan Prop23 { get; set; } + public Guid Prop24 { get; set; } + public Uri Prop25 { get; set; } + public Version Prop26 { get; set; } + public DayOfWeek Prop27 { get; set; } + public byte[] Prop28 { get; set; } + public int Prop29 { get; set; } + public DateTime Prop30 { get; set; } + } + } + """; await VerifyAgainstBaselineUsingFile("Primitives.generated.txt", source); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs index cbbd34e7fc41d..b5c8cc534f693 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs @@ -105,12 +105,19 @@ private static async Task VerifyAgainstBaselineUsingF ExtensionClassType extType = ExtensionClassType.None, ExpectedDiagnostics expectedDiags = ExpectedDiagnostics.None) { + string environmentSubFolder = +#if NETCOREAPP + "netcoreapp" +#else + "net462" +#endif + ; string path = extType is ExtensionClassType.None - ? Path.Combine("Baselines", filename) - : Path.Combine("Baselines", extType.ToString(), filename); - string baseline = LineEndingsHelper.Normalize(await File.ReadAllTextAsync(path).ConfigureAwait(false)); + ? Path.Combine("Baselines", environmentSubFolder, filename) + : Path.Combine("Baselines", environmentSubFolder, extType.ToString(), filename); + string baseline = LineEndingsHelper.Normalize(File.ReadAllText(path)); string[] expectedLines = baseline.Replace("%VERSION%", typeof(ConfigurationBindingGenerator).Assembly.GetName().Version?.ToString()) - .Split(Environment.NewLine); + .Split(new string[] { Environment.NewLine }, StringSplitOptions.None); ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); result.ValidateDiagnostics(expectedDiags); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs index d93607d376399..741ed47317940 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs @@ -36,7 +36,7 @@ public async Task LangVersionMustBeCharp12OrHigher(LanguageVersion langVersion) Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] public async Task ValueTypesAreInvalidAsBindInputs() { string source = """ @@ -225,7 +225,7 @@ async Task Test(bool expectOutput) } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] [ActiveIssue("Work out why we aren't getting all the expected diagnostics.")] public async Task IssueDiagnosticsForAllOffendingCallsites() { 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 848d93b32a475..1580309ec0d67 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 @@ -48,8 +48,8 @@ - - + + PreserveNewest