diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/README.md b/src/libraries/Microsoft.Extensions.Configuration.Binder/README.md index 142e14aefe84f..e70fbe4b0bff8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/README.md +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/README.md @@ -118,13 +118,36 @@ Sometimes the SDK uses stale bits of the generator. This can lead to unexpected Some contributions might change the logic emitted by the generator. We maintain baseline [source files](https://github.com/dotnet/runtime/tree/e3e9758a10870a8f99a93a25e54ab2837d3abefc/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines) to track the code emitted to handle some core binding scenarios. -If the emitted code changes, these tests will fail locally and in continuous integration checks (for PRs) changes. You would need to update the baseline source files, manually or by using the following commands (PowerShell): +If the emitted code changes, these tests will fail locally and\or during continuous integration checks. You would need to update the baseline source files, manually or by using a combination of: +- The `/p:UpdateBaselines=true` switch when building `Microsoft.Extensions.Configuration.Binder` in order to use `InterceptableAttributeVersion` for testing and\or updating the baselines. +- The `/p:UpdateBaselines=true` switch when building `Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests` in order to update the test baseline files. +- The `RepoRootDir` environment variable. +- The optional `InterceptableAttributeVersion` environment variable. +The `RepoRootDir environment variable needs to be specified to the root repo path. + +The `InterceptableAttributeVersion` specifies what version of the `[Interceptable]` attribute should be generated. Currently there are two versions, both of which are experimental as of July 2024, and one is selected based on the local compiler. The original version ("version 0") is expected to be deprecated. Version 1 will be used for newer compilers automatically. However, if version 0 needs to be updated when newer compilers are present, version 0 can be forced by setting the environment variable to `0`. + +Sample commands (PowerShell): ```ps > $env:RepoRootDir = "D:\repos\dotnet_runtime" -> dotnet build t:test -f /p:UpdateBaselines=true +> $env:InterceptableAttributeVersion = 0 # NOTE: this is optional - see notes +> cd D:/repros/dotnet_runtime/src/libraries/Microsoft.Extensions.Configuration.Binder +> dotnet build /p:UpdateBaselines=true +> cd tests/SourceGenerationTests +> dotnet build -t:test /p:UpdateBaselines=true ``` -We have a [test helper](https://github.com/dotnet/runtime/blob/e3e9758a10870a8f99a93a25e54ab2837d3abefc/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs#L105-L118) to update the baselines. It requires setting an environment variable called `RepoRootDir` to the root repo path. In additon, the `UpdateBaselines` MSBuild property needs to be set to `true`. +Sample commands (command prompt): +``` +set RepoRootDir = "D:\repos\dotnet_runtime" +set InterceptableAttributeVersion = 0 REM NOTE: this is optional - see notes +cd D:\repros\dotnet_runtime\src\libraries\Microsoft.Extensions.Configuration.Binder +dotnet build /p:UpdateBaselines=true +cd tests\SourceGenerationTests +dotnet build -t:test /p:UpdateBaselines=true +``` -After updating the baselines, inspect the changes to verify that they are valid. Note that the baseline tests will fail if the new code causes errors when building the resulting compilation. \ No newline at end of file +After updating the baselines: +- Inspect the changes to verify that they are valid. Note that the baseline tests will fail if the new code causes errors when building the resulting compilation. +- Rebuild `Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests` without `/p:UpdateBaselines=true` so that the tests compare against the new baselines instead of being re-generated. 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 1dceb31a0cc8a..a5de49c031636 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs @@ -72,22 +72,28 @@ file static class {{Identifier.BindingExtensions}} private void EmitInterceptsLocationAttrDecl() { _writer.WriteLine(); + + string arguments = ConfigurationBindingGenerator.InterceptorVersion == 0 ? + "string filePath, int line, int column" : + "int version, string data"; + _writer.WriteLine($$""" - namespace System.Runtime.CompilerServices - { - using System; - using System.CodeDom.Compiler; + namespace System.Runtime.CompilerServices + { + using System; + using System.CodeDom.Compiler; - {{Expression.GeneratedCodeAnnotation}} - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute + {{Expression.GeneratedCodeAnnotation}} + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute({{arguments}}) { - public InterceptsLocationAttribute(string filePath, int line, int column) - { - } } } - """); + } + """); + _writer.WriteLine(); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs index aa94f864eb8f2..4a3d5bbf7dea8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs @@ -3,8 +3,12 @@ //#define LAUNCH_DEBUGGER using System; +using System.Diagnostics; +using System.Reflection; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -62,6 +66,69 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .WithTrackingName(GenSpecTrackingName); context.RegisterSourceOutput(genSpec, ReportDiagnosticsAndEmitSource); + + if (!s_hasInitializedInterceptorVersion) + { + InterceptorVersion = DetermineInterceptableVersion(); + s_hasInitializedInterceptorVersion = true; + } + } + + internal static int InterceptorVersion { get; private set; } + + // Used with v1 interceptor lightup approach: + private static bool s_hasInitializedInterceptorVersion; + internal static Func? GetInterceptableLocationFunc { get; private set; } + internal static MethodInfo? InterceptableLocationVersionGetDisplayLocation { get; private set; } + internal static MethodInfo? InterceptableLocationDataGetter { get; private set; } + internal static MethodInfo? InterceptableLocationVersionGetter { get; private set; } + + internal static int DetermineInterceptableVersion() + { + MethodInfo? getInterceptableLocationMethod = null; + int? interceptableVersion = null; + +#if UPDATE_BASELINES +#pragma warning disable RS1035 // Do not use APIs banned for analyzers + string? interceptableVersionString = Environment.GetEnvironmentVariable("InterceptableAttributeVersion"); +#pragma warning restore RS1035 + if (interceptableVersionString is not null) + { + if (int.TryParse(interceptableVersionString, out int version) && (version == 0 || version == 1)) + { + interceptableVersion = version; + } + else + { + throw new InvalidOperationException($"Invalid InterceptableAttributeVersion value: {interceptableVersionString}"); + } + } + + if (interceptableVersion is null || interceptableVersion == 1) +#endif + { + getInterceptableLocationMethod = typeof(Microsoft.CodeAnalysis.CSharp.CSharpExtensions).GetMethod( + "GetInterceptableLocation", + BindingFlags.Static | BindingFlags.Public, + binder: null, + new Type[] { typeof(SemanticModel), typeof(InvocationExpressionSyntax), typeof(CancellationToken) }, + modifiers: Array.Empty()); + + interceptableVersion = getInterceptableLocationMethod is null ? 0 : 1; + } + + if (interceptableVersion == 1) + { + GetInterceptableLocationFunc = (Func) + getInterceptableLocationMethod.CreateDelegate(typeof(Func), target: null); + + Type? interceptableLocationType = typeof(Microsoft.CodeAnalysis.CSharp.CSharpExtensions).Assembly.GetType("Microsoft.CodeAnalysis.CSharp.InterceptableLocation"); + InterceptableLocationVersionGetDisplayLocation = interceptableLocationType.GetMethod("GetDisplayLocation", BindingFlags.Instance | BindingFlags.Public); + InterceptableLocationVersionGetter = interceptableLocationType.GetProperty("Version", BindingFlags.Instance | BindingFlags.Public).GetGetMethod(); + InterceptableLocationDataGetter = interceptableLocationType.GetProperty("Data", BindingFlags.Instance | BindingFlags.Public).GetGetMethod(); + } + + return interceptableVersion.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 cf56aae81ba47..9d340ac0e93c5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs @@ -158,9 +158,19 @@ overload is MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions || private void EmitInterceptsLocationAnnotations(IEnumerable infoList) { - foreach (InvocationLocationInfo info in infoList) + if (ConfigurationBindingGenerator.InterceptorVersion == 0) { - _writer.WriteLine($@"[{Identifier.InterceptsLocation}(@""{info.FilePath}"", {info.LineNumber}, {info.CharacterNumber})]"); + foreach (InvocationLocationInfo info in infoList) + { + _writer.WriteLine($@"[{Identifier.InterceptsLocation}(@""{info.FilePath}"", {info.LineNumber}, {info.CharacterNumber})]"); + } + } + else + { + foreach (InvocationLocationInfo info in infoList) + { + _writer.WriteLine($@"[{Identifier.InterceptsLocation}({info.InterceptableLocationVersion}, ""{info.InterceptableLocationData}"")] // {info.InterceptableLocationGetDisplayLocation()}"); + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj index 8f1008a51202d..9d57cc249077e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj @@ -7,6 +7,7 @@ cs true $(DefineConstants);LAUNCH_DEBUGGER + $(DefineConstants);UPDATE_BASELINES diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs index 999ed6514f99d..5b4f903db7c21 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; @@ -170,33 +172,56 @@ public InvocationLocationInfo(MethodsToGen interceptor, IInvocationOperation inv { Debug.Assert(BinderInvocation.IsBindingOperation(invocation)); - if (invocation.Syntax is not InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccessExprSyntax }) + if (invocation.Syntax is not InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccessExprSyntax } invocationExpressionSyntax) { const string InvalidInvocationErrMsg = "The invocation should have been validated upstream when selecting invocations to emit interceptors for."; throw new ArgumentException(InvalidInvocationErrMsg, nameof(invocation)); } - SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; - TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; - FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); - - Interceptor = interceptor; - LineNumber = linePosSpan.StartLinePosition.Line + 1; - CharacterNumber = linePosSpan.StartLinePosition.Character + 1; - FilePath = GetInterceptorFilePath(); - - // Use the same logic used by the interceptors API for resolving the source mapped value of a path. - // https://github.com/dotnet/roslyn/blob/f290437fcc75dad50a38c09e0977cce13a64f5ba/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs#L1063-L1064 - string GetInterceptorFilePath() + if (ConfigurationBindingGenerator.InterceptorVersion == 0) + { + SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; + TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; + FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); + + Interceptor = interceptor; + LineNumber = linePosSpan.StartLinePosition.Line + 1; + CharacterNumber = linePosSpan.StartLinePosition.Character + 1; + FilePath = GetInterceptorFilePath(); + + // Use the same logic used by the interceptors API for resolving the source mapped value of a path. + // https://github.com/dotnet/roslyn/blob/f290437fcc75dad50a38c09e0977cce13a64f5ba/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs#L1063-L1064 + string GetInterceptorFilePath() + { + SourceReferenceResolver? sourceReferenceResolver = invocation.SemanticModel?.Compilation.Options.SourceReferenceResolver; + return sourceReferenceResolver?.NormalizePath(operationSyntaxTree.FilePath, baseFilePath: null) ?? operationSyntaxTree.FilePath; + } + } + else { - SourceReferenceResolver? sourceReferenceResolver = invocation.SemanticModel?.Compilation.Options.SourceReferenceResolver; - return sourceReferenceResolver?.NormalizePath(operationSyntaxTree.FilePath, baseFilePath: null) ?? operationSyntaxTree.FilePath; + Debug.Assert(ConfigurationBindingGenerator.InterceptorVersion == 1); + Interceptor = interceptor; + InterceptableLocation = ConfigurationBindingGenerator.GetInterceptableLocationFunc(invocation.SemanticModel, invocationExpressionSyntax, default(CancellationToken)); } } public MethodsToGen Interceptor { get; } + + // Used with v0 interceptor approach: public string FilePath { get; } public int LineNumber { get; } public int CharacterNumber { get; } + + // Used with v1 interceptor approach: + private object? InterceptableLocation { get; } + + public string InterceptableLocationGetDisplayLocation() => InterceptableLocation is null ? "" : + (string)ConfigurationBindingGenerator.InterceptableLocationVersionGetDisplayLocation.Invoke(InterceptableLocation, parameters: null); + + public string InterceptableLocationData => InterceptableLocation is null ? "" : + (string)ConfigurationBindingGenerator.InterceptableLocationDataGetter.Invoke(InterceptableLocation, parameters: null); + + public int InterceptableLocationVersion => InterceptableLocation is null ? 0 : + (int)ConfigurationBindingGenerator.InterceptableLocationVersionGetter.Invoke(InterceptableLocation, parameters: null); } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind_Instance.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_Instance.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind_Instance.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind_Instance_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind_Instance_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind_Key_Instance.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_Key_Instance.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind_Key_Instance.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind_ParseTypeFromMethodParam.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Bind_ParseTypeFromMethodParam.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_T_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue_T_Key.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_T_Key.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue_T_Key.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue_T_Key_DefaultValue.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue_T_Key_DefaultValue.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue_TypeOf_Key.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue_TypeOf_Key.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue_TypeOf_Key_DefaultValue.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/GetValue_TypeOf_Key_DefaultValue.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_PrimitivesOnly.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_PrimitivesOnly.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_PrimitivesOnly.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_T.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_T.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_T.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_T_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_T_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_T_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_TypeOf.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_TypeOf.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_TypeOf.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_TypeOf_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version0/Get_TypeOf_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt new file mode 100644 index 0000000000000..6b35d5b73afd4 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt @@ -0,0 +1,243 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "/TzDbopkyui/vWzNJfmpq0IBAABzcmMtMC5jcw==")] // src-0.cs(12,14) + 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 = (global::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(1, "/TzDbopkyui/vWzNJfmpq2YBAABzcmMtMC5jcw==")] // src-0.cs(13,20) + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? instance, Action? configureOptions) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (instance is null) + { + return; + } + + var typedObj = (global::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(1, "/TzDbopkyui/vWzNJfmpq5oBAABzcmMtMC5jcw==")] // src-0.cs(14,20) + public static void Bind_ProgramMyClass(this IConfiguration configuration, string key, object? instance) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (instance is null) + { + return; + } + + var typedObj = (global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::System.Collections.Generic.Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out global::Program.MyClass2? element) && element is not null)) + { + element = new global::Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + global::System.Collections.Generic.List? temp4 = instance.MyList; + temp4 ??= new global::System.Collections.Generic.List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt new file mode 100644 index 0000000000000..ed6db21e40afc --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt @@ -0,0 +1,189 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "4Xh0YE/VYvgbhgXo3wbvQmABAABzcmMtMC5jcw==")] // src-0.cs(12,20) + 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 = (global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::System.Collections.Generic.Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out global::Program.MyClass2? element) && element is not null)) + { + element = new global::Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + global::System.Collections.Generic.List? temp4 = instance.MyList; + temp4 ??= new global::System.Collections.Generic.List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt new file mode 100644 index 0000000000000..c8abf9d79fb7d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt @@ -0,0 +1,207 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "7CkSkJNSgE0UIXT00R2Pg2ABAABzcmMtMC5jcw==")] // src-0.cs(12,20) + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? instance, Action? configureOptions) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (instance is null) + { + return; + } + + var typedObj = (global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::System.Collections.Generic.Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out global::Program.MyClass2? element) && element is not null)) + { + element = new global::Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + global::System.Collections.Generic.List? temp4 = instance.MyList; + temp4 ??= new global::System.Collections.Generic.List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt new file mode 100644 index 0000000000000..ee106169a27ee --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt @@ -0,0 +1,189 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "Y9gUhj+FktjqOlmO7sDlc2ABAABzcmMtMC5jcw==")] // src-0.cs(12,20) + public static void Bind_ProgramMyClass(this IConfiguration configuration, string key, object? instance) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (instance is null) + { + return; + } + + var typedObj = (global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::System.Collections.Generic.Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out global::Program.MyClass2? element) && element is not null)) + { + element = new global::Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + global::System.Collections.Generic.List? temp4 = instance.MyList; + temp4 ??= new global::System.Collections.Generic.List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt new file mode 100644 index 0000000000000..9f8f951bff298 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt @@ -0,0 +1,76 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "T1w3acs59wB13yghJH3pNu0BAABzcmMtMC5jcw==")] // 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(1, "T1w3acs59wB13yghJH3pNpACAABzcmMtMC5jcw==")] // 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(1, "T1w3acs59wB13yghJH3pNikDAABzcmMtMC5jcw==")] // 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/net462/ConfigurationBinder/Version1/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt new file mode 100644 index 0000000000000..61b94ba858cb0 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt @@ -0,0 +1,264 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "ybRgWwDRfhqdJiXObv1EploBAABzcmMtMC5jcw==")] // 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(1, "ybRgWwDRfhqdJiXObv1EptsBAABzcmMtMC5jcw==")] // 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(1, "ybRgWwDRfhqdJiXObv1EpqEBAABzcmMtMC5jcw==")] // 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(1, "ybRgWwDRfhqdJiXObv1Epi0CAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(global::Program.MyClass)) + { + var instance = new global::Program.MyClass(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + else if (type == typeof(global::Program.MyClass2)) + { + var instance = new global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.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) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp13; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + + /// 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue.generated.txt new file mode 100644 index 0000000000000..02e16bbfddd1d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue.generated.txt @@ -0,0 +1,137 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "FUU/7viOc6J4+bBdF0LYFmoBAABzcmMtMC5jcw==")] // 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(1, "FUU/7viOc6J4+bBdF0LYFg4CAABzcmMtMC5jcw==")] // 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(1, "FUU/7viOc6J4+bBdF0LYFpcBAABzcmMtMC5jcw==")] // 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(1, "FUU/7viOc6J4+bBdF0LYFk4CAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(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(global::System.Globalization.CultureInfo)) + { + return ParseSystemGlobalizationCultureInfo(value, section.Path); + } + + return null; + } + + public static int ParseInt(string value, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + + public static bool ParseBool(string value, string? path) + { + try + { + return bool.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + } + } + + public static byte[] ParseByteArray(string value, string? path) + { + try + { + return Convert.FromBase64String(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte[])}'.", exception); + } + } + + public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) + { + try + { + return CultureInfo.GetCultureInfo(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt new file mode 100644 index 0000000000000..1c6888d831b7e --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt @@ -0,0 +1,77 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "l6og4h0k+MCB+NyRUYPpBRMBAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt new file mode 100644 index 0000000000000..63cc9ff7ef3f1 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt @@ -0,0 +1,77 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "HtCNcyU56cGzgOS+3mQ3hFkBAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt new file mode 100644 index 0000000000000..a0c635eca06b9 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt @@ -0,0 +1,77 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "Ct86MC6P/J4XmWvHG331ExMBAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(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, string? path) + { + try + { + return bool.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt new file mode 100644 index 0000000000000..9a1655ddd5bee --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt @@ -0,0 +1,77 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "2f9jaIbaGLAmi4UfgTaavjMBAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + IConfigurationSection section = configuration.GetSection(key); + + if (section.Value is not string value) + { + return null; + } + + if (type == typeof(global::System.Globalization.CultureInfo)) + { + return ParseSystemGlobalizationCultureInfo(value, section.Path); + } + + return null; + } + + public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) + { + try + { + return CultureInfo.GetCultureInfo(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt new file mode 100644 index 0000000000000..8ac6d07d29c65 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt @@ -0,0 +1,186 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "t6tvUrc1mCV/SdIkmk1VLPMAAABzcmMtMC5jcw==")] // 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(1, "t6tvUrc1mCV/SdIkmk1VLDIBAABzcmMtMC5jcw==")] // 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(1, "t6tvUrc1mCV/SdIkmk1VLA4BAABzcmMtMC5jcw==")] // 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(1, "t6tvUrc1mCV/SdIkmk1VLGMBAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + + public static float ParseFloat(string value, string? path) + { + try + { + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + } + } + + public static double ParseDouble(string value, string? path) + { + try + { + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt new file mode 100644 index 0000000000000..205783d6a9821 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt @@ -0,0 +1,231 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "2zgoF+/xjOaOQ0zF63jTZk0BAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(global::Program.MyClass)) + { + var instance = new global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value2) + { + instance.MyString = value2; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value3) + { + instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + { + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.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) + { + global::System.Collections.Generic.Dictionary? temp12 = instance.MyDictionary; + temp12 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt new file mode 100644 index 0000000000000..8b4644d2b27c9 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt @@ -0,0 +1,231 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "G/3QeDCtvtw8eMmVQBFB100BAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(global::Program.MyClass)) + { + var instance = new global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value2) + { + instance.MyString = value2; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value3) + { + instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + { + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.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) + { + global::System.Collections.Generic.Dictionary? temp12 = instance.MyDictionary; + temp12 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt new file mode 100644 index 0000000000000..09c568050ea1e --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt @@ -0,0 +1,155 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "izerIO0FCy8FKR4lwenwv1gBAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(global::Program.MyClass2)) + { + var instance = new global::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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + + /// 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt new file mode 100644 index 0000000000000..3d3c4340e9719 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt @@ -0,0 +1,155 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "E7RMXqTP0g6z7RR21Qk0nTkBAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(global::Program.MyClass2)) + { + var instance = new global::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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + + /// 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version0/BindConfiguration.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/BindConfiguration.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version0/BindConfiguration.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version0/BindConfigurationWithConfigureActions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version0/BindConfigurationWithConfigureActions.generated.txt new file mode 100644 index 0000000000000..9d8efcab38e62 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version0/BindConfigurationWithConfigureActions.generated.txt @@ -0,0 +1,218 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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 + { + if (optionsBuilder is null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); + } + + if (configSectionPath is null) + { + throw new ArgumentNullException(nameof(configSectionPath)); + } + + optionsBuilder.Configure((instance, config) => + { + if (config is null) + { + throw new ArgumentNullException(nameof(config)); + } + + IConfiguration section = string.Equals(string.Empty, configSectionPath, StringComparison.OrdinalIgnoreCase) ? config : config.GetSection(configSectionPath); + BindCoreMain(section, instance, typeof(TOptions), configureBinder); + }); + + optionsBuilder.Services.AddSingleton, ConfigurationChangeTokenSource>(sp => + { + return new ConfigurationChangeTokenSource(optionsBuilder.Name, sp.GetRequiredService()); + }); + + 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version0/Bind_T.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Bind_T.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version0/Bind_T.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version0/Bind_T_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Bind_T_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version0/Bind_T_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt new file mode 100644 index 0000000000000..12dc3a77b0ed3 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt @@ -0,0 +1,218 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "dQtLTBW+V+KExKSKfSGYWHgBAABzcmMtMC5jcw==")] // src-0.cs(12,24) + public static OptionsBuilder BindConfiguration(this OptionsBuilder optionsBuilder, string configSectionPath, Action? configureBinder = null) where TOptions : class + { + if (optionsBuilder is null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); + } + + if (configSectionPath is null) + { + throw new ArgumentNullException(nameof(configSectionPath)); + } + + optionsBuilder.Configure((instance, config) => + { + if (config is null) + { + throw new ArgumentNullException(nameof(config)); + } + + IConfiguration section = string.Equals(string.Empty, configSectionPath, StringComparison.OrdinalIgnoreCase) ? config : config.GetSection(configSectionPath); + BindCoreMain(section, instance, typeof(TOptions), configureBinder); + }); + + optionsBuilder.Services.AddSingleton, ConfigurationChangeTokenSource>(sp => + { + return new ConfigurationChangeTokenSource(optionsBuilder.Name, sp.GetRequiredService()); + }); + + 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt new file mode 100644 index 0000000000000..103423d5b0bcd --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt @@ -0,0 +1,218 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "+juUY8RZzi0MOViHimSQBXgBAABzcmMtMC5jcw==")] // src-0.cs(12,24) + public static OptionsBuilder BindConfiguration(this OptionsBuilder optionsBuilder, string configSectionPath, Action? configureBinder = null) where TOptions : class + { + if (optionsBuilder is null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); + } + + if (configSectionPath is null) + { + throw new ArgumentNullException(nameof(configSectionPath)); + } + + optionsBuilder.Configure((instance, config) => + { + if (config is null) + { + throw new ArgumentNullException(nameof(config)); + } + + IConfiguration section = string.Equals(string.Empty, configSectionPath, StringComparison.OrdinalIgnoreCase) ? config : config.GetSection(configSectionPath); + BindCoreMain(section, instance, typeof(TOptions), configureBinder); + }); + + optionsBuilder.Services.AddSingleton, ConfigurationChangeTokenSource>(sp => + { + return new ConfigurationChangeTokenSource(optionsBuilder.Name, sp.GetRequiredService()); + }); + + 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt new file mode 100644 index 0000000000000..4ca0ba31f4aca --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt @@ -0,0 +1,224 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "/HkAEr/Ik8W47keDiXaNkeEBAABzcmMtMC5jcw==")] // 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 + { + if (optionsBuilder is null) + { + throw new ArgumentNullException(nameof(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 + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (config is null) + { + throw new ArgumentNullException(nameof(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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt new file mode 100644 index 0000000000000..a8cc253765c04 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt @@ -0,0 +1,218 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "eAEQHTx/qUZMzyaWapEG3uEBAABzcmMtMC5jcw==")] // src-0.cs(15,24) + public static OptionsBuilder Bind(this OptionsBuilder optionsBuilder, IConfiguration config, Action? configureBinder) where TOptions : class + { + if (optionsBuilder is null) + { + throw new ArgumentNullException(nameof(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 + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (config is null) + { + throw new ArgumentNullException(nameof(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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version0/Configure_T.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version0/Configure_T.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version0/Configure_T_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version0/Configure_T_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version0/Configure_T_name.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T_name.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version0/Configure_T_name.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version0/Configure_T_name_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Configure_T_name_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version0/Configure_T_name_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt new file mode 100644 index 0000000000000..417f221d0aabd --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt @@ -0,0 +1,267 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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.Linq; + 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(1, "AkrFtcV3uQ2TMBvVEqDKnLcBAABzcmMtMC5jcw==")] // 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 + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (config is null) + { + throw new ArgumentNullException(nameof(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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any()) + { + continue; + } + var value = new global::Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + global::System.Collections.Generic.List? temp10 = instance.MyList2; + temp10 ??= new global::System.Collections.Generic.List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt new file mode 100644 index 0000000000000..98580f73b919f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt @@ -0,0 +1,267 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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.Linq; + 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(1, "rlE/o+amAiG6/WEDLRfo0rcBAABzcmMtMC5jcw==")] // 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 + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (config is null) + { + throw new ArgumentNullException(nameof(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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any()) + { + continue; + } + var value = new global::Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + global::System.Collections.Generic.List? temp10 = instance.MyList2; + temp10 ??= new global::System.Collections.Generic.List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt new file mode 100644 index 0000000000000..25d182f7ac1fb --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt @@ -0,0 +1,267 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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.Linq; + 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(1, "1LKp35372BShtJj3xCs0ibcBAABzcmMtMC5jcw==")] // 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 + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (config is null) + { + throw new ArgumentNullException(nameof(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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any()) + { + continue; + } + var value = new global::Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + global::System.Collections.Generic.List? temp10 = instance.MyList2; + temp10 ??= new global::System.Collections.Generic.List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt new file mode 100644 index 0000000000000..7e4337e45c143 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt @@ -0,0 +1,261 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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.Linq; + 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(1, "GVTnXyPUwpMq46hK7kk0ULcBAABzcmMtMC5jcw==")] // src-0.cs(14,18) + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (config is null) + { + throw new ArgumentNullException(nameof(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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any()) + { + continue; + } + var value = new global::Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + global::System.Collections.Generic.List? temp10 = instance.MyList2; + temp10 ??= new global::System.Collections.Generic.List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version0/Collections.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Collections.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version0/Collections.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/DefaultConstructorParameters.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version0/DefaultConstructorParameters.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/DefaultConstructorParameters.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version0/DefaultConstructorParameters.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version0/EmptyConfigType.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/EmptyConfigType.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version0/EmptyConfigType.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/Version0/Primitives.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Primitives.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version0/Primitives.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/UnsupportedTypes.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version0/UnsupportedTypes.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/UnsupportedTypes.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version0/UnsupportedTypes.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt new file mode 100644 index 0000000000000..14f60bde81175 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt @@ -0,0 +1,233 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "x0/k4h2yDpHi1YoDOFMOUFoBAABzcmMtMC5jcw==")] // 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) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(global::Program.MyClassWithCustomCollections)) + { + var instance = new global::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 global::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 global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::Program.MyClassWithCustomCollections instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1) + { + global::Program.CustomDictionary? temp3 = instance.CustomDictionary; + temp3 ??= new global::Program.CustomDictionary(); + BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); + instance.CustomDictionary = temp3; + } + + if (AsConfigWithChildren(configuration.GetSection("CustomList")) is IConfigurationSection section4) + { + global::Program.CustomList? temp6 = instance.CustomList; + temp6 ??= new global::Program.CustomList(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.CustomList = temp6; + } + + if (AsConfigWithChildren(configuration.GetSection("IReadOnlyList")) is IConfigurationSection section7) + { + global::System.Collections.Generic.IReadOnlyList? temp9 = instance.IReadOnlyList; + temp9 = temp9 is null ? (global::System.Collections.Generic.IReadOnlyList)new List() : (global::System.Collections.Generic.IReadOnlyList)new List(temp9); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.IReadOnlyList = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("IReadOnlyDictionary")) is IConfigurationSection section10) + { + global::System.Collections.Generic.IReadOnlyDictionary? temp12 = instance.IReadOnlyDictionary; + temp12 = temp12 is null ? (global::System.Collections.Generic.IReadOnlyDictionary)new Dictionary() : (global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt new file mode 100644 index 0000000000000..dbcadc11d177a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt @@ -0,0 +1,249 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "NfQsvTvxsxzujBWqpTrJJGIBAABzcmMtMC5jcw==")] // src-0.cs(13,16) + public static void Bind_ProgramClassWhereParametersHaveDefaultValue(this IConfiguration configuration, object? instance) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (instance is null) + { + return; + } + + var typedObj = (global::Program.ClassWhereParametersHaveDefaultValue)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramClassWhereParametersHaveDefaultValue = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Name", "Address", "Age", "F", "D", "M", "SC", "C", "NAge", "NF", "ND", "NM", "NSC", "NC" }); + + public static void BindCore(IConfiguration configuration, ref global::Program.ClassWhereParametersHaveDefaultValue instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.ClassWhereParametersHaveDefaultValue), s_configKeys_ProgramClassWhereParametersHaveDefaultValue, configuration, binderOptions); + } + + public static global::Program.ClassWhereParametersHaveDefaultValue InitializeProgramClassWhereParametersHaveDefaultValue(IConfiguration configuration, BinderOptions? binderOptions) + { + string name = "John Doe"!; + if (configuration["Name"] is string value0) + { + name = value0; + } + + string address = "1 Microsoft Way"!; + if (configuration["Address"] is string value1) + { + address = value1; + } + + int age = (int)(42); + if (configuration["Age"] is string value2) + { + age = ParseInt(value2, configuration.GetSection("Age").Path); + } + + float f = 42F; + if (configuration["F"] is string value3) + { + f = ParseFloat(value3, configuration.GetSection("F").Path); + } + + double d = 3.1415899999999999D; + if (configuration["D"] is string value4) + { + d = ParseDouble(value4, configuration.GetSection("D").Path); + } + + decimal m = 3.1415926535897932384626433M; + if (configuration["M"] is string value5) + { + m = ParseDecimal(value5, configuration.GetSection("M").Path); + } + + global::System.StringComparison sc = (global::System.StringComparison)(4); + if (configuration["SC"] is string value6) + { + sc = ParseEnum(value6, configuration.GetSection("SC").Path); + } + + char c = 'q'; + if (configuration["C"] is string value7) + { + c = ParseChar(value7, configuration.GetSection("C").Path); + } + + int? nage = (int?)(42); + if (configuration["NAge"] is string value8) + { + nage = ParseInt(value8, configuration.GetSection("NAge").Path); + } + + float? nf = 42F; + if (configuration["NF"] is string value9) + { + nf = ParseFloat(value9, configuration.GetSection("NF").Path); + } + + double? nd = 3.1415899999999999D; + if (configuration["ND"] is string value10) + { + nd = ParseDouble(value10, configuration.GetSection("ND").Path); + } + + decimal? nm = 3.1415926535897932384626433M; + if (configuration["NM"] is string value11) + { + nm = ParseDecimal(value11, configuration.GetSection("NM").Path); + } + + global::System.StringComparison? nsc = (global::System.StringComparison?)(4); + if (configuration["NSC"] is string value12) + { + nsc = ParseEnum(value12, configuration.GetSection("NSC").Path); + } + + char? nc = 'q'; + if (configuration["NC"] is string value13) + { + nc = ParseChar(value13, configuration.GetSection("NC").Path); + } + + return new global::Program.ClassWhereParametersHaveDefaultValue(name, address, age, f, d, m, sc, c, nage, nf, nd, nm, nsc, nc); + } + + + /// 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, string? path) 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 '{path}' to type '{typeof(T)}'.", exception); + } + } + + public static int ParseInt(string value, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + + public static float ParseFloat(string value, string? path) + { + try + { + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + } + } + + public static double ParseDouble(string value, string? path) + { + try + { + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + } + } + + public static decimal ParseDecimal(string value, string? path) + { + try + { + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(decimal)}'.", exception); + } + } + + public static char ParseChar(string value, string? path) + { + try + { + return char.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(char)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt new file mode 100644 index 0000000000000..b9ca5b5e42e50 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt @@ -0,0 +1,109 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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 given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(1, "GOBbBAa/JyGKryQ8GhUEQzQBAABzcmMtMC5jcw==")] // 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(1, "GOBbBAa/JyGKryQ8GhUEQ4YBAABzcmMtMC5jcw==")] // src-0.cs(15,23) + public static void Bind_TypeWithNoMembers_Wrapper(this IConfiguration configuration, object? instance) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (instance is null) + { + return; + } + + var typedObj = (global::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 global::TypeWithNoMembers_Wrapper instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::TypeWithNoMembers_Wrapper), s_configKeys_TypeWithNoMembers_Wrapper, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("Member")) is IConfigurationSection section0) + { + instance.Member ??= new global::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/net462/Version1/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt new file mode 100644 index 0000000000000..57e6403dc8268 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt @@ -0,0 +1,610 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "afrwH3rJzSfhL67foIuQbjoBAABzcmMtMC5jcw==")] // 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 = (global::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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.Prop0; + } + + if (configuration["Prop1"] is string value1) + { + instance.Prop1 = ParseByte(value1, configuration.GetSection("Prop1").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop1 = instance.Prop1; + } + + if (configuration["Prop2"] is string value2) + { + instance.Prop2 = ParseSbyte(value2, configuration.GetSection("Prop2").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop2 = instance.Prop2; + } + + if (configuration["Prop3"] is string value3) + { + instance.Prop3 = ParseChar(value3, configuration.GetSection("Prop3").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop3 = instance.Prop3; + } + + if (configuration["Prop4"] is string value4) + { + instance.Prop4 = ParseDouble(value4, configuration.GetSection("Prop4").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop4 = instance.Prop4; + } + + if (configuration["Prop5"] is string value5) + { + instance.Prop5 = value5; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop5; + if (currentValue is not null) + { + instance.Prop5 = currentValue; + } + } + + if (configuration["Prop6"] is string value6) + { + instance.Prop6 = ParseInt(value6, configuration.GetSection("Prop6").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop6 = instance.Prop6; + } + + if (configuration["Prop8"] is string value7) + { + instance.Prop8 = ParseShort(value7, configuration.GetSection("Prop8").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop8 = instance.Prop8; + } + + if (configuration["Prop9"] is string value8) + { + instance.Prop9 = ParseLong(value8, configuration.GetSection("Prop9").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop9 = instance.Prop9; + } + + if (configuration["Prop10"] is string value9) + { + instance.Prop10 = ParseFloat(value9, configuration.GetSection("Prop10").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop10 = instance.Prop10; + } + + if (configuration["Prop13"] is string value10) + { + instance.Prop13 = ParseUshort(value10, configuration.GetSection("Prop13").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop13 = instance.Prop13; + } + + if (configuration["Prop14"] is string value11) + { + instance.Prop14 = ParseUint(value11, configuration.GetSection("Prop14").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop14 = instance.Prop14; + } + + if (configuration["Prop15"] is string value12) + { + instance.Prop15 = ParseUlong(value12, configuration.GetSection("Prop15").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop15 = instance.Prop15; + } + + if (configuration["Prop16"] is string value13) + { + instance.Prop16 = value13; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop16; + if (currentValue is not null) + { + instance.Prop16 = currentValue; + } + } + + if (configuration["Prop17"] is string value14) + { + instance.Prop17 = ParseSystemGlobalizationCultureInfo(value14, configuration.GetSection("Prop17").Path); + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop17; + if (currentValue is not null) + { + instance.Prop17 = currentValue; + } + } + + if (configuration["Prop19"] is string value15) + { + instance.Prop19 = ParseSystemDateTime(value15, configuration.GetSection("Prop19").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop19 = instance.Prop19; + } + + if (configuration["Prop20"] is string value16) + { + instance.Prop20 = ParseSystemDateTimeOffset(value16, configuration.GetSection("Prop20").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop20 = instance.Prop20; + } + + if (configuration["Prop21"] is string value17) + { + instance.Prop21 = ParseDecimal(value17, configuration.GetSection("Prop21").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop21 = instance.Prop21; + } + + if (configuration["Prop23"] is string value18) + { + instance.Prop23 = ParseSystemTimeSpan(value18, configuration.GetSection("Prop23").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop23 = instance.Prop23; + } + + if (configuration["Prop24"] is string value19) + { + instance.Prop24 = ParseSystemGuid(value19, configuration.GetSection("Prop24").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop24 = instance.Prop24; + } + + if (configuration["Prop25"] is string value20) + { + instance.Prop25 = ParseSystemUri(value20, configuration.GetSection("Prop25").Path); + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop25; + if (currentValue is not null) + { + instance.Prop25 = currentValue; + } + } + + if (configuration["Prop26"] is string value21) + { + instance.Prop26 = ParseSystemVersion(value21, configuration.GetSection("Prop26").Path); + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop26; + if (currentValue is not null) + { + instance.Prop26 = currentValue; + } + } + + if (configuration["Prop27"] is string value22) + { + instance.Prop27 = ParseEnum(value22, configuration.GetSection("Prop27").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop27 = instance.Prop27; + } + + if (configuration["Prop28"] is string value23) + { + instance.Prop28 = ParseByteArray(value23, configuration.GetSection("Prop28").Path); + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop28; + if (currentValue is not null) + { + instance.Prop28 = currentValue; + } + } + + if (configuration["Prop29"] is string value24) + { + instance.Prop29 = ParseInt(value24, configuration.GetSection("Prop29").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop29 = instance.Prop29; + } + + if (configuration["Prop30"] is string value25) + { + instance.Prop30 = ParseSystemDateTime(value25, configuration.GetSection("Prop30").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop30 = instance.Prop30; + } + } + + + /// 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, string? path) 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 '{path}' to type '{typeof(T)}'.", exception); + } + } + + public static bool ParseBool(string value, string? path) + { + try + { + return bool.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + } + } + + public static byte ParseByte(string value, string? path) + { + try + { + return byte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte)}'.", exception); + } + } + + public static sbyte ParseSbyte(string value, string? path) + { + try + { + return sbyte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(sbyte)}'.", exception); + } + } + + public static char ParseChar(string value, string? path) + { + try + { + return char.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(char)}'.", exception); + } + } + + public static double ParseDouble(string value, string? path) + { + try + { + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + } + } + + public static int ParseInt(string value, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + + public static short ParseShort(string value, string? path) + { + try + { + return short.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(short)}'.", exception); + } + } + + public static long ParseLong(string value, string? path) + { + try + { + return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(long)}'.", exception); + } + } + + public static float ParseFloat(string value, string? path) + { + try + { + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + } + } + + public static ushort ParseUshort(string value, string? path) + { + try + { + return ushort.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(ushort)}'.", exception); + } + } + + public static uint ParseUint(string value, string? path) + { + try + { + return uint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(uint)}'.", exception); + } + } + + public static ulong ParseUlong(string value, string? path) + { + try + { + return ulong.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(ulong)}'.", exception); + } + } + + public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) + { + try + { + return CultureInfo.GetCultureInfo(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + } + } + + public static global::System.DateTime ParseSystemDateTime(string value, string? path) + { + try + { + return global::System.DateTime.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateTime)}'.", exception); + } + } + + public static global::System.DateTimeOffset ParseSystemDateTimeOffset(string value, string? path) + { + try + { + return global::System.DateTimeOffset.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateTimeOffset)}'.", exception); + } + } + + public static decimal ParseDecimal(string value, string? path) + { + try + { + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(decimal)}'.", exception); + } + } + + public static global::System.TimeSpan ParseSystemTimeSpan(string value, string? path) + { + try + { + return global::System.TimeSpan.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.TimeSpan)}'.", exception); + } + } + + public static global::System.Guid ParseSystemGuid(string value, string? path) + { + try + { + return global::System.Guid.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Guid)}'.", exception); + } + } + + public static global::System.Uri ParseSystemUri(string value, string? path) + { + try + { + return new Uri(value, UriKind.RelativeOrAbsolute); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Uri)}'.", exception); + } + } + + public static global::System.Version ParseSystemVersion(string value, string? path) + { + try + { + return global::System.Version.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Version)}'.", exception); + } + } + + public static byte[] ParseByteArray(string value, string? path) + { + try + { + return Convert.FromBase64String(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte[])}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt new file mode 100644 index 0000000000000..9f784cc0193dd --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt @@ -0,0 +1,266 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "R+J95cfVt+aeGOS40ZJnXPoBAABzcmMtMC5jcw==")] // src-0.cs(18,23) + [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXFICAABzcmMtMC5jcw==")] // src-0.cs(21,23) + [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXH0CAABzcmMtMC5jcw==")] // src-0.cs(22,15) + public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXNsBAABzcmMtMC5jcw==")] // src-0.cs(17,23) + public static void Bind_Options(this IConfiguration configuration, object? instance) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (instance is null) + { + return; + } + + var typedObj = (global::Options)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_RecordAction = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "x" }); + private readonly static Lazy> s_configKeys_Options = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Name", "Age", "List", "Array", "Record" }); + + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(global::Options)) + { + var instance = new global::Options(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + else if (type == typeof(global::System.Collections.Generic.List)) + { + } + else if (type == typeof(global::MyDictionary)) + { + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List 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 string[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + var temp1 = new List(); + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + temp1.Add(value); + } + } + + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp1.Count); + temp1.CopyTo(instance, originalCount); + } + + public static void BindCore(IConfiguration configuration, ref global::Record instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Record), s_configKeys_RecordAction, configuration, binderOptions); + } + + public static void BindCore(IConfiguration configuration, ref global::Options instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Options), s_configKeys_Options, configuration, binderOptions); + + if (configuration["Name"] is string value2) + { + instance.Name = value2; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Name; + if (currentValue is not null) + { + instance.Name = currentValue; + } + } + + if (configuration["Age"] is string value3) + { + instance.Age = ParseInt(value3, configuration.GetSection("Age").Path); + } + else if (defaultValueIfNotFound) + { + instance.Age = instance.Age; + } + + if (AsConfigWithChildren(configuration.GetSection("List")) is IConfigurationSection section4) + { + global::System.Collections.Generic.List? temp6 = instance.List; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.List = temp6; + } + + if (AsConfigWithChildren(configuration.GetSection("Array")) is IConfigurationSection section7) + { + string[]? temp9 = instance.Array; + temp9 ??= new string[0]; + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.Array = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("Record")) is IConfigurationSection section10) + { + global::Record? temp12 = instance.Record; + temp12 ??= InitializeRecordAction(section10, binderOptions); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.Record = temp12; + } + } + + public static global::Record InitializeRecordAction(IConfiguration configuration, BinderOptions? binderOptions) + { + int x = (int)(10); + if (configuration["x"] is string value13) + { + x = ParseInt(value13, configuration.GetSection("x").Path); + } + + return new global::Record(x) + { + x = x, + }; + } + + + /// 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' 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/Version0/Bind.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Bind.generated.txt 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/Version0/Bind_Instance.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Instance.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Bind_Instance.generated.txt 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/Version0/Bind_Instance_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Bind_Instance_BinderOptions.generated.txt 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/Version0/Bind_Key_Instance.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_Key_Instance.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Bind_Key_Instance.generated.txt 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/Version0/Bind_ParseTypeFromMethodParam.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Bind_ParseTypeFromMethodParam.generated.txt 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/Version0/Get.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Get.generated.txt 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/Version0/GetValue.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/GetValue.generated.txt 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/Version0/GetValue_T_Key.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_T_Key.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/GetValue_T_Key.generated.txt 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/Version0/GetValue_T_Key_DefaultValue.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/GetValue_T_Key_DefaultValue.generated.txt 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/Version0/GetValue_TypeOf_Key.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/GetValue_TypeOf_Key.generated.txt 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/Version0/GetValue_TypeOf_Key_DefaultValue.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/GetValue_TypeOf_Key_DefaultValue.generated.txt 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/Version0/Get_PrimitivesOnly.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_PrimitivesOnly.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Get_PrimitivesOnly.generated.txt 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/Version0/Get_T.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_T.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Get_T.generated.txt 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/Version0/Get_T_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_T_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Get_T_BinderOptions.generated.txt 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/Version0/Get_TypeOf.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_TypeOf.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Get_TypeOf.generated.txt 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/Version0/Get_TypeOf_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version0/Get_TypeOf_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt new file mode 100644 index 0000000000000..baca17797df7a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt @@ -0,0 +1,234 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "/TzDbopkyui/vWzNJfmpq0IBAABzcmMtMC5jcw==")] // 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 = (global::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(1, "/TzDbopkyui/vWzNJfmpq2YBAABzcmMtMC5jcw==")] // 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 = (global::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(1, "/TzDbopkyui/vWzNJfmpq5oBAABzcmMtMC5jcw==")] // 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 = (global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::System.Collections.Generic.Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out global::Program.MyClass2? element) && element is not null)) + { + element = new global::Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + global::System.Collections.Generic.List? temp4 = instance.MyList; + temp4 ??= new global::System.Collections.Generic.List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt new file mode 100644 index 0000000000000..55fc88c6e54f7 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt @@ -0,0 +1,186 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "4Xh0YE/VYvgbhgXo3wbvQmABAABzcmMtMC5jcw==")] // 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 = (global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::System.Collections.Generic.Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out global::Program.MyClass2? element) && element is not null)) + { + element = new global::Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + global::System.Collections.Generic.List? temp4 = instance.MyList; + temp4 ??= new global::System.Collections.Generic.List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt new file mode 100644 index 0000000000000..a1c971d891ea5 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt @@ -0,0 +1,204 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "7CkSkJNSgE0UIXT00R2Pg2ABAABzcmMtMC5jcw==")] // 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 = (global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::System.Collections.Generic.Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out global::Program.MyClass2? element) && element is not null)) + { + element = new global::Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + global::System.Collections.Generic.List? temp4 = instance.MyList; + temp4 ??= new global::System.Collections.Generic.List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt new file mode 100644 index 0000000000000..b3aa0044f9228 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt @@ -0,0 +1,186 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "Y9gUhj+FktjqOlmO7sDlc2ABAABzcmMtMC5jcw==")] // 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 = (global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::System.Collections.Generic.Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out global::Program.MyClass2? element) && element is not null)) + { + element = new global::Program.MyClass2(); + } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + global::System.Collections.Generic.List? temp4 = instance.MyList; + temp4 ??= new global::System.Collections.Generic.List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt new file mode 100644 index 0000000000000..9f8f951bff298 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt @@ -0,0 +1,76 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "T1w3acs59wB13yghJH3pNu0BAABzcmMtMC5jcw==")] // 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(1, "T1w3acs59wB13yghJH3pNpACAABzcmMtMC5jcw==")] // 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(1, "T1w3acs59wB13yghJH3pNikDAABzcmMtMC5jcw==")] // 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/Version1/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt new file mode 100644 index 0000000000000..25f336d16d22d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt @@ -0,0 +1,261 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "ybRgWwDRfhqdJiXObv1EploBAABzcmMtMC5jcw==")] // 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(1, "ybRgWwDRfhqdJiXObv1EptsBAABzcmMtMC5jcw==")] // 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(1, "ybRgWwDRfhqdJiXObv1EpqEBAABzcmMtMC5jcw==")] // 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(1, "ybRgWwDRfhqdJiXObv1Epi0CAABzcmMtMC5jcw==")] // 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(global::Program.MyClass)) + { + var instance = new global::Program.MyClass(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + else if (type == typeof(global::Program.MyClass2)) + { + var instance = new global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.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) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp13; + } + } + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + + /// 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue.generated.txt new file mode 100644 index 0000000000000..dcea57b57f330 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue.generated.txt @@ -0,0 +1,136 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "FUU/7viOc6J4+bBdF0LYFmoBAABzcmMtMC5jcw==")] // 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(1, "FUU/7viOc6J4+bBdF0LYFg4CAABzcmMtMC5jcw==")] // src-0.cs(16,24) + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(defaultValue))] + 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(1, "FUU/7viOc6J4+bBdF0LYFpcBAABzcmMtMC5jcw==")] // 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(1, "FUU/7viOc6J4+bBdF0LYFk4CAABzcmMtMC5jcw==")] // src-0.cs(17,24) + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(defaultValue))] + 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(global::System.Globalization.CultureInfo)) + { + return ParseSystemGlobalizationCultureInfo(value, section.Path); + } + + return null; + } + + public static int ParseInt(string value, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + + public static bool ParseBool(string value, string? path) + { + try + { + return bool.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + } + } + + public static byte[] ParseByteArray(string value, string? path) + { + try + { + return Convert.FromBase64String(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte[])}'.", exception); + } + } + + public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) + { + try + { + return CultureInfo.GetCultureInfo(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt new file mode 100644 index 0000000000000..73542f63c1952 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt @@ -0,0 +1,74 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "l6og4h0k+MCB+NyRUYPpBRMBAABzcmMtMC5jcw==")] // 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt new file mode 100644 index 0000000000000..25b3911f5e538 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt @@ -0,0 +1,75 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "HtCNcyU56cGzgOS+3mQ3hFkBAABzcmMtMC5jcw==")] // src-0.cs(12,20) + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(defaultValue))] + 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt new file mode 100644 index 0000000000000..23cb1d5fa3f55 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt @@ -0,0 +1,74 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "Ct86MC6P/J4XmWvHG331ExMBAABzcmMtMC5jcw==")] // 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, string? path) + { + try + { + return bool.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt new file mode 100644 index 0000000000000..992acd8e2a528 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt @@ -0,0 +1,75 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "2f9jaIbaGLAmi4UfgTaavjMBAABzcmMtMC5jcw==")] // src-0.cs(11,20) + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(defaultValue))] + 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(global::System.Globalization.CultureInfo)) + { + return ParseSystemGlobalizationCultureInfo(value, section.Path); + } + + return null; + } + + public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) + { + try + { + return CultureInfo.GetCultureInfo(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt new file mode 100644 index 0000000000000..7ce53871d7d68 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt @@ -0,0 +1,183 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "t6tvUrc1mCV/SdIkmk1VLPMAAABzcmMtMC5jcw==")] // 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(1, "t6tvUrc1mCV/SdIkmk1VLDIBAABzcmMtMC5jcw==")] // 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(1, "t6tvUrc1mCV/SdIkmk1VLA4BAABzcmMtMC5jcw==")] // 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(1, "t6tvUrc1mCV/SdIkmk1VLGMBAABzcmMtMC5jcw==")] // 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + + public static float ParseFloat(string value, string? path) + { + try + { + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + } + } + + public static double ParseDouble(string value, string? path) + { + try + { + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt new file mode 100644 index 0000000000000..ae367c81fd068 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt @@ -0,0 +1,228 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "2zgoF+/xjOaOQ0zF63jTZk0BAABzcmMtMC5jcw==")] // 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(global::Program.MyClass)) + { + var instance = new global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value2) + { + instance.MyString = value2; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value3) + { + instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + { + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.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) + { + global::System.Collections.Generic.Dictionary? temp12 = instance.MyDictionary; + temp12 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt new file mode 100644 index 0000000000000..938d15f5734da --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt @@ -0,0 +1,228 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "G/3QeDCtvtw8eMmVQBFB100BAABzcmMtMC5jcw==")] // 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(global::Program.MyClass)) + { + var instance = new global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value2) + { + instance.MyString = value2; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value3) + { + instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + { + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.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) + { + global::System.Collections.Generic.Dictionary? temp12 = instance.MyDictionary; + temp12 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt new file mode 100644 index 0000000000000..096d659c11774 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt @@ -0,0 +1,152 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "izerIO0FCy8FKR4lwenwv1gBAABzcmMtMC5jcw==")] // 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(global::Program.MyClass2)) + { + var instance = new global::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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + + /// 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt new file mode 100644 index 0000000000000..2d338610619c8 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt @@ -0,0 +1,152 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "E7RMXqTP0g6z7RR21Qk0nTkBAABzcmMtMC5jcw==")] // 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(global::Program.MyClass2)) + { + var instance = new global::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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + + /// 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #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/Version0/BindConfiguration.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/BindConfiguration.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version0/BindConfiguration.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version0/BindConfigurationWithConfigureActions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version0/BindConfigurationWithConfigureActions.generated.txt new file mode 100644 index 0000000000000..776a7f171230a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version0/BindConfigurationWithConfigureActions.generated.txt @@ -0,0 +1,209 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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>(sp => + { + return new ConfigurationChangeTokenSource(optionsBuilder.Name, sp.GetRequiredService()); + }); + + 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' 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/Version0/Bind_T.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Bind_T.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version0/Bind_T.generated.txt 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/Version0/Bind_T_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Bind_T_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version0/Bind_T_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt new file mode 100644 index 0000000000000..3bf1c7fffb3d1 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt @@ -0,0 +1,209 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "dQtLTBW+V+KExKSKfSGYWHgBAABzcmMtMC5jcw==")] // 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>(sp => + { + return new ConfigurationChangeTokenSource(optionsBuilder.Name, sp.GetRequiredService()); + }); + + 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt new file mode 100644 index 0000000000000..68fd25284f96d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt @@ -0,0 +1,209 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "+juUY8RZzi0MOViHimSQBXgBAABzcmMtMC5jcw==")] // 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>(sp => + { + return new ConfigurationChangeTokenSource(optionsBuilder.Name, sp.GetRequiredService()); + }); + + 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt new file mode 100644 index 0000000000000..79b3242d6bda9 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt @@ -0,0 +1,215 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "/HkAEr/Ik8W47keDiXaNkeEBAABzcmMtMC5jcw==")] // 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt new file mode 100644 index 0000000000000..8451a8071657a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt @@ -0,0 +1,209 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "eAEQHTx/qUZMzyaWapEG3uEBAABzcmMtMC5jcw==")] // 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value1) + { + instance.MyString = value1; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value2) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + { + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' 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.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version0/Configure_T.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version0/Configure_T.generated.txt 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/Version0/Configure_T_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version0/Configure_T_BinderOptions.generated.txt 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/Version0/Configure_T_name.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_name.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version0/Configure_T_name.generated.txt 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/Version0/Configure_T_name_BinderOptions.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Configure_T_name_BinderOptions.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version0/Configure_T_name_BinderOptions.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt new file mode 100644 index 0000000000000..4acfc2c04b2a4 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt @@ -0,0 +1,261 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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.Linq; + 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(1, "AkrFtcV3uQ2TMBvVEqDKnLcBAABzcmMtMC5jcw==")] // 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any()) + { + continue; + } + var value = new global::Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + global::System.Collections.Generic.List? temp10 = instance.MyList2; + temp10 ??= new global::System.Collections.Generic.List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt new file mode 100644 index 0000000000000..99c67141401cd --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt @@ -0,0 +1,261 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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.Linq; + 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(1, "rlE/o+amAiG6/WEDLRfo0rcBAABzcmMtMC5jcw==")] // 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any()) + { + continue; + } + var value = new global::Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + global::System.Collections.Generic.List? temp10 = instance.MyList2; + temp10 ??= new global::System.Collections.Generic.List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt new file mode 100644 index 0000000000000..07fa717a30168 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt @@ -0,0 +1,261 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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.Linq; + 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(1, "1LKp35372BShtJj3xCs0ibcBAABzcmMtMC5jcw==")] // 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any()) + { + continue; + } + var value = new global::Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + global::System.Collections.Generic.List? temp10 = instance.MyList2; + temp10 ??= new global::System.Collections.Generic.List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt new file mode 100644 index 0000000000000..20a36e76d994d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt @@ -0,0 +1,255 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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.Linq; + 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(1, "GVTnXyPUwpMq46hK7kk0ULcBAABzcmMtMC5jcw==")] // 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(global::Program.MyClass)) + { + var temp = (global::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 global::System.Collections.Generic.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 global::Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.MyInt; + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any()) + { + continue; + } + var value = new global::Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.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 global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value3) + { + instance.MyString = value3; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.MyString; + if (currentValue is not null) + { + instance.MyString = currentValue; + } + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = instance.MyInt; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + { + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + { + global::System.Collections.Generic.List? temp10 = instance.MyList2; + temp10 ??= new global::System.Collections.Generic.List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} 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/Version0/Collections.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Collections.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version0/Collections.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/DefaultConstructorParameters.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version0/DefaultConstructorParameters.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/DefaultConstructorParameters.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version0/DefaultConstructorParameters.generated.txt 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/Version0/EmptyConfigType.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/EmptyConfigType.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version0/EmptyConfigType.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version0/Primitives.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Primitives.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version0/Primitives.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/UnsupportedTypes.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version0/UnsupportedTypes.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/UnsupportedTypes.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version0/UnsupportedTypes.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt new file mode 100644 index 0000000000000..9b690cd988893 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt @@ -0,0 +1,230 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "x0/k4h2yDpHi1YoDOFMOUFoBAABzcmMtMC5jcw==")] // 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(global::Program.MyClassWithCustomCollections)) + { + var instance = new global::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 global::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 global::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 global::System.Collections.Generic.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 global::System.Collections.Generic.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 global::Program.MyClassWithCustomCollections instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1) + { + global::Program.CustomDictionary? temp3 = instance.CustomDictionary; + temp3 ??= new global::Program.CustomDictionary(); + BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); + instance.CustomDictionary = temp3; + } + + if (AsConfigWithChildren(configuration.GetSection("CustomList")) is IConfigurationSection section4) + { + global::Program.CustomList? temp6 = instance.CustomList; + temp6 ??= new global::Program.CustomList(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.CustomList = temp6; + } + + if (AsConfigWithChildren(configuration.GetSection("IReadOnlyList")) is IConfigurationSection section7) + { + global::System.Collections.Generic.IReadOnlyList? temp9 = instance.IReadOnlyList; + temp9 = temp9 is null ? (global::System.Collections.Generic.IReadOnlyList)new List() : (global::System.Collections.Generic.IReadOnlyList)new List(temp9); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.IReadOnlyList = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("IReadOnlyDictionary")) is IConfigurationSection section10) + { + global::System.Collections.Generic.IReadOnlyDictionary? temp12 = instance.IReadOnlyDictionary; + temp12 = temp12 is null ? (global::System.Collections.Generic.IReadOnlyDictionary)new Dictionary() : (global::System.Collections.Generic.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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt new file mode 100644 index 0000000000000..57fabba9d1faf --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt @@ -0,0 +1,246 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "NfQsvTvxsxzujBWqpTrJJGIBAABzcmMtMC5jcw==")] // src-0.cs(13,16) + public static void Bind_ProgramClassWhereParametersHaveDefaultValue(this IConfiguration configuration, object? instance) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (global::Program.ClassWhereParametersHaveDefaultValue)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramClassWhereParametersHaveDefaultValue = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Name", "Address", "Age", "F", "D", "M", "SC", "C", "NAge", "NF", "ND", "NM", "NSC", "NC" }); + + public static void BindCore(IConfiguration configuration, ref global::Program.ClassWhereParametersHaveDefaultValue instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Program.ClassWhereParametersHaveDefaultValue), s_configKeys_ProgramClassWhereParametersHaveDefaultValue, configuration, binderOptions); + } + + public static global::Program.ClassWhereParametersHaveDefaultValue InitializeProgramClassWhereParametersHaveDefaultValue(IConfiguration configuration, BinderOptions? binderOptions) + { + string name = "John Doe"!; + if (configuration["Name"] is string value0) + { + name = value0; + } + + string address = "1 Microsoft Way"!; + if (configuration["Address"] is string value1) + { + address = value1; + } + + int age = (int)(42); + if (configuration["Age"] is string value2) + { + age = ParseInt(value2, configuration.GetSection("Age").Path); + } + + float f = 42F; + if (configuration["F"] is string value3) + { + f = ParseFloat(value3, configuration.GetSection("F").Path); + } + + double d = 3.1415899999999999D; + if (configuration["D"] is string value4) + { + d = ParseDouble(value4, configuration.GetSection("D").Path); + } + + decimal m = 3.1415926535897932384626433M; + if (configuration["M"] is string value5) + { + m = ParseDecimal(value5, configuration.GetSection("M").Path); + } + + global::System.StringComparison sc = (global::System.StringComparison)(4); + if (configuration["SC"] is string value6) + { + sc = ParseEnum(value6, configuration.GetSection("SC").Path); + } + + char c = 'q'; + if (configuration["C"] is string value7) + { + c = ParseChar(value7, configuration.GetSection("C").Path); + } + + int? nage = (int?)(42); + if (configuration["NAge"] is string value8) + { + nage = ParseInt(value8, configuration.GetSection("NAge").Path); + } + + float? nf = 42F; + if (configuration["NF"] is string value9) + { + nf = ParseFloat(value9, configuration.GetSection("NF").Path); + } + + double? nd = 3.1415899999999999D; + if (configuration["ND"] is string value10) + { + nd = ParseDouble(value10, configuration.GetSection("ND").Path); + } + + decimal? nm = 3.1415926535897932384626433M; + if (configuration["NM"] is string value11) + { + nm = ParseDecimal(value11, configuration.GetSection("NM").Path); + } + + global::System.StringComparison? nsc = (global::System.StringComparison?)(4); + if (configuration["NSC"] is string value12) + { + nsc = ParseEnum(value12, configuration.GetSection("NSC").Path); + } + + char? nc = 'q'; + if (configuration["NC"] is string value13) + { + nc = ParseChar(value13, configuration.GetSection("NC").Path); + } + + return new global::Program.ClassWhereParametersHaveDefaultValue(name, address, age, f, d, m, sc, c, nage, nf, nd, nm, nsc, nc); + } + + + /// 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, string? path) where T : struct + { + try + { + return Enum.Parse(value, ignoreCase: true); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(T)}'.", exception); + } + } + + public static int ParseInt(string value, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + + public static float ParseFloat(string value, string? path) + { + try + { + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + } + } + + public static double ParseDouble(string value, string? path) + { + try + { + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + } + } + + public static decimal ParseDecimal(string value, string? path) + { + try + { + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(decimal)}'.", exception); + } + } + + public static char ParseChar(string value, string? path) + { + try + { + return char.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(char)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt new file mode 100644 index 0000000000000..82b035f6dbf63 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt @@ -0,0 +1,106 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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 given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(1, "GOBbBAa/JyGKryQ8GhUEQzQBAABzcmMtMC5jcw==")] // 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(1, "GOBbBAa/JyGKryQ8GhUEQ4YBAABzcmMtMC5jcw==")] // 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 = (global::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 global::TypeWithNoMembers_Wrapper instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::TypeWithNoMembers_Wrapper), s_configKeys_TypeWithNoMembers_Wrapper, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("Member")) is IConfigurationSection section0) + { + instance.Member ??= new global::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/Version1/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt new file mode 100644 index 0000000000000..7e442555da5e1 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt @@ -0,0 +1,712 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "4gHRpL26b2ISfu2l3hr4fToBAABzcmMtMC5jcw==")] // src-0.cs(13,16) + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? instance) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (global::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", "Prop7", "Prop11", "Prop12", "Prop18", "Prop22", "Prop28", "Prop29", "Prop30" }); + + public static void BindCore(IConfiguration configuration, ref global::Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::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 = instance.Prop0; + } + + if (configuration["Prop1"] is string value1) + { + instance.Prop1 = ParseByte(value1, configuration.GetSection("Prop1").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop1 = instance.Prop1; + } + + if (configuration["Prop2"] is string value2) + { + instance.Prop2 = ParseSbyte(value2, configuration.GetSection("Prop2").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop2 = instance.Prop2; + } + + if (configuration["Prop3"] is string value3) + { + instance.Prop3 = ParseChar(value3, configuration.GetSection("Prop3").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop3 = instance.Prop3; + } + + if (configuration["Prop4"] is string value4) + { + instance.Prop4 = ParseDouble(value4, configuration.GetSection("Prop4").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop4 = instance.Prop4; + } + + if (configuration["Prop5"] is string value5) + { + instance.Prop5 = value5; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop5; + if (currentValue is not null) + { + instance.Prop5 = currentValue; + } + } + + if (configuration["Prop6"] is string value6) + { + instance.Prop6 = ParseInt(value6, configuration.GetSection("Prop6").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop6 = instance.Prop6; + } + + if (configuration["Prop8"] is string value7) + { + instance.Prop8 = ParseShort(value7, configuration.GetSection("Prop8").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop8 = instance.Prop8; + } + + if (configuration["Prop9"] is string value8) + { + instance.Prop9 = ParseLong(value8, configuration.GetSection("Prop9").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop9 = instance.Prop9; + } + + if (configuration["Prop10"] is string value9) + { + instance.Prop10 = ParseFloat(value9, configuration.GetSection("Prop10").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop10 = instance.Prop10; + } + + if (configuration["Prop13"] is string value10) + { + instance.Prop13 = ParseUshort(value10, configuration.GetSection("Prop13").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop13 = instance.Prop13; + } + + if (configuration["Prop14"] is string value11) + { + instance.Prop14 = ParseUint(value11, configuration.GetSection("Prop14").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop14 = instance.Prop14; + } + + if (configuration["Prop15"] is string value12) + { + instance.Prop15 = ParseUlong(value12, configuration.GetSection("Prop15").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop15 = instance.Prop15; + } + + if (configuration["Prop16"] is string value13) + { + instance.Prop16 = value13; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop16; + if (currentValue is not null) + { + instance.Prop16 = currentValue; + } + } + + if (configuration["Prop17"] is string value14) + { + instance.Prop17 = ParseSystemGlobalizationCultureInfo(value14, configuration.GetSection("Prop17").Path); + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop17; + if (currentValue is not null) + { + instance.Prop17 = currentValue; + } + } + + if (configuration["Prop19"] is string value15) + { + instance.Prop19 = ParseSystemDateTime(value15, configuration.GetSection("Prop19").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop19 = instance.Prop19; + } + + if (configuration["Prop20"] is string value16) + { + instance.Prop20 = ParseSystemDateTimeOffset(value16, configuration.GetSection("Prop20").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop20 = instance.Prop20; + } + + if (configuration["Prop21"] is string value17) + { + instance.Prop21 = ParseDecimal(value17, configuration.GetSection("Prop21").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop21 = instance.Prop21; + } + + if (configuration["Prop23"] is string value18) + { + instance.Prop23 = ParseSystemTimeSpan(value18, configuration.GetSection("Prop23").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop23 = instance.Prop23; + } + + if (configuration["Prop24"] is string value19) + { + instance.Prop24 = ParseSystemGuid(value19, configuration.GetSection("Prop24").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop24 = instance.Prop24; + } + + if (configuration["Prop25"] is string value20) + { + instance.Prop25 = ParseSystemUri(value20, configuration.GetSection("Prop25").Path); + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop25; + if (currentValue is not null) + { + instance.Prop25 = currentValue; + } + } + + if (configuration["Prop26"] is string value21) + { + instance.Prop26 = ParseSystemVersion(value21, configuration.GetSection("Prop26").Path); + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop26; + if (currentValue is not null) + { + instance.Prop26 = currentValue; + } + } + + if (configuration["Prop27"] is string value22) + { + instance.Prop27 = ParseEnum(value22, configuration.GetSection("Prop27").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop27 = instance.Prop27; + } + + if (configuration["Prop7"] is string value23) + { + instance.Prop7 = ParseSystemInt128(value23, configuration.GetSection("Prop7").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop7 = instance.Prop7; + } + + if (configuration["Prop11"] is string value24) + { + instance.Prop11 = ParseSystemHalf(value24, configuration.GetSection("Prop11").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop11 = instance.Prop11; + } + + if (configuration["Prop12"] is string value25) + { + instance.Prop12 = ParseSystemUInt128(value25, configuration.GetSection("Prop12").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop12 = instance.Prop12; + } + + if (configuration["Prop18"] is string value26) + { + instance.Prop18 = ParseSystemDateOnly(value26, configuration.GetSection("Prop18").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop18 = instance.Prop18; + } + + if (configuration["Prop22"] is string value27) + { + instance.Prop22 = ParseSystemTimeOnly(value27, configuration.GetSection("Prop22").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop22 = instance.Prop22; + } + + if (configuration["Prop28"] is string value28) + { + instance.Prop28 = ParseByteArray(value28, configuration.GetSection("Prop28").Path); + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Prop28; + if (currentValue is not null) + { + instance.Prop28 = currentValue; + } + } + + if (configuration["Prop29"] is string value29) + { + instance.Prop29 = ParseInt(value29, configuration.GetSection("Prop29").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop29 = instance.Prop29; + } + + if (configuration["Prop30"] is string value30) + { + instance.Prop30 = ParseSystemDateTime(value30, configuration.GetSection("Prop30").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop30 = instance.Prop30; + } + } + + + /// 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, string? path) where T : struct + { + try + { + return Enum.Parse(value, ignoreCase: true); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(T)}'.", exception); + } + } + + public static bool ParseBool(string value, string? path) + { + try + { + return bool.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + } + } + + public static byte ParseByte(string value, string? path) + { + try + { + return byte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte)}'.", exception); + } + } + + public static sbyte ParseSbyte(string value, string? path) + { + try + { + return sbyte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(sbyte)}'.", exception); + } + } + + public static char ParseChar(string value, string? path) + { + try + { + return char.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(char)}'.", exception); + } + } + + public static double ParseDouble(string value, string? path) + { + try + { + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + } + } + + public static int ParseInt(string value, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + + public static short ParseShort(string value, string? path) + { + try + { + return short.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(short)}'.", exception); + } + } + + public static long ParseLong(string value, string? path) + { + try + { + return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(long)}'.", exception); + } + } + + public static float ParseFloat(string value, string? path) + { + try + { + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + } + } + + public static ushort ParseUshort(string value, string? path) + { + try + { + return ushort.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(ushort)}'.", exception); + } + } + + public static uint ParseUint(string value, string? path) + { + try + { + return uint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(uint)}'.", exception); + } + } + + public static ulong ParseUlong(string value, string? path) + { + try + { + return ulong.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(ulong)}'.", exception); + } + } + + public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) + { + try + { + return CultureInfo.GetCultureInfo(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + } + } + + public static global::System.DateTime ParseSystemDateTime(string value, string? path) + { + try + { + return global::System.DateTime.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateTime)}'.", exception); + } + } + + public static global::System.DateTimeOffset ParseSystemDateTimeOffset(string value, string? path) + { + try + { + return global::System.DateTimeOffset.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateTimeOffset)}'.", exception); + } + } + + public static decimal ParseDecimal(string value, string? path) + { + try + { + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(decimal)}'.", exception); + } + } + + public static global::System.TimeSpan ParseSystemTimeSpan(string value, string? path) + { + try + { + return global::System.TimeSpan.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.TimeSpan)}'.", exception); + } + } + + public static global::System.Guid ParseSystemGuid(string value, string? path) + { + try + { + return global::System.Guid.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Guid)}'.", exception); + } + } + + public static global::System.Uri ParseSystemUri(string value, string? path) + { + try + { + return new Uri(value, UriKind.RelativeOrAbsolute); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Uri)}'.", exception); + } + } + + public static global::System.Version ParseSystemVersion(string value, string? path) + { + try + { + return global::System.Version.Parse(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Version)}'.", exception); + } + } + + public static global::System.Int128 ParseSystemInt128(string value, string? path) + { + try + { + return global::System.Int128.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Int128)}'.", exception); + } + } + + public static global::System.Half ParseSystemHalf(string value, string? path) + { + try + { + return global::System.Half.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Half)}'.", exception); + } + } + + public static global::System.UInt128 ParseSystemUInt128(string value, string? path) + { + try + { + return global::System.UInt128.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.UInt128)}'.", exception); + } + } + + public static global::System.DateOnly ParseSystemDateOnly(string value, string? path) + { + try + { + return global::System.DateOnly.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateOnly)}'.", exception); + } + } + + public static global::System.TimeOnly ParseSystemTimeOnly(string value, string? path) + { + try + { + return global::System.TimeOnly.Parse(value, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.TimeOnly)}'.", exception); + } + } + + public static byte[] ParseByteArray(string value, string? path) + { + try + { + return Convert.FromBase64String(value); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte[])}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt new file mode 100644 index 0000000000000..509dbb05c39c8 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt @@ -0,0 +1,260 @@ +// + +#nullable enable annotations +#nullable disable warnings + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0612, CS0618 + +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(int version, string data) + { + } + } +} + +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(1, "R+J95cfVt+aeGOS40ZJnXPoBAABzcmMtMC5jcw==")] // src-0.cs(18,23) + [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXFICAABzcmMtMC5jcw==")] // src-0.cs(21,23) + [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXH0CAABzcmMtMC5jcw==")] // src-0.cs(22,15) + public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXNsBAABzcmMtMC5jcw==")] // src-0.cs(17,23) + public static void Bind_Options(this IConfiguration configuration, object? instance) + { + ArgumentNullException.ThrowIfNull(configuration); + + if (instance is null) + { + return; + } + + var typedObj = (global::Options)instance; + BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_RecordAction = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "x" }); + private readonly static Lazy> s_configKeys_Options = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Name", "Age", "List", "Array", "Record" }); + + 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(global::Options)) + { + var instance = new global::Options(); + BindCore(configuration, ref instance, defaultValueIfNotFound: true, binderOptions); + return instance; + } + else if (type == typeof(global::System.Collections.Generic.List)) + { + } + else if (type == typeof(global::MyDictionary)) + { + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref global::System.Collections.Generic.List 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 string[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + var temp1 = new List(); + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + temp1.Add(value); + } + } + + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp1.Count); + temp1.CopyTo(instance, originalCount); + } + + public static void BindCore(IConfiguration configuration, ref global::Record instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Record), s_configKeys_RecordAction, configuration, binderOptions); + } + + public static void BindCore(IConfiguration configuration, ref global::Options instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(global::Options), s_configKeys_Options, configuration, binderOptions); + + if (configuration["Name"] is string value2) + { + instance.Name = value2; + } + else if (defaultValueIfNotFound) + { + var currentValue = instance.Name; + if (currentValue is not null) + { + instance.Name = currentValue; + } + } + + if (configuration["Age"] is string value3) + { + instance.Age = ParseInt(value3, configuration.GetSection("Age").Path); + } + else if (defaultValueIfNotFound) + { + instance.Age = instance.Age; + } + + if (AsConfigWithChildren(configuration.GetSection("List")) is IConfigurationSection section4) + { + global::System.Collections.Generic.List? temp6 = instance.List; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.List = temp6; + } + + if (AsConfigWithChildren(configuration.GetSection("Array")) is IConfigurationSection section7) + { + string[]? temp9 = instance.Array; + temp9 ??= new string[0]; + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.Array = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("Record")) is IConfigurationSection section10) + { + global::Record? temp12 = instance.Record; + temp12 ??= InitializeRecordAction(section10, binderOptions); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.Record = temp12; + } + } + + public static global::Record InitializeRecordAction(IConfiguration configuration, BinderOptions? binderOptions) + { + int x = (int)(10); + if (configuration["x"] is string value13) + { + x = ParseInt(value13, configuration.GetSection("x").Path); + } + + return new global::Record(x) + { + x = x, + }; + } + + + /// 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, string? path) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + } + } + #endregion Core binding extensions. + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.Options.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.Options.cs index 4480ab4066882..219c57b9e5913 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.Options.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.Options.cs @@ -202,7 +202,36 @@ public class MyClass """; await VerifyAgainstBaselineUsingFile("BindConfiguration.generated.txt", GetSource(), extType: ExtensionClassType.OptionsBuilder); - await VerifyAgainstBaselineUsingFile("BindConfiguration.generated.txt", GetSource(@", _ => { }"), extType: ExtensionClassType.OptionsBuilder); + } + + [Fact] + public async Task BindConfigurationWithConfigureActions() + { + string GetSource(string? configureActions = null) => $$""" + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + + public class Program + { + public static void Main() + { + var services = new ServiceCollection(); + OptionsBuilder optionsBuilder = new(services, Options.DefaultName); + optionsBuilder.BindConfiguration(""{{configureActions}}); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + } + } + """; + + await VerifyAgainstBaselineUsingFile("BindConfigurationWithConfigureActions.generated.txt", GetSource(@", _ => { }"), extType: ExtensionClassType.OptionsBuilder); } #endregion OptionsBuilder extensions. } 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 e9dcad70de4f9..19c170bfcd4bd 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 @@ -9,9 +9,11 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Binder.SourceGeneration; @@ -99,6 +101,25 @@ private static async Task VerifyThatSourceIsGenerated(string testSourceCode) Assert.True(source.Value.SourceText.Lines.Count > 10); } + private static bool s_initializedInterceptorVersion; + private static int s_interceptorVersion; + private static int GetInterceptorVersion() + { + if (!s_initializedInterceptorVersion) + { + MethodInfo method = typeof(ConfigurationBindingGenerator).GetMethod( + "DetermineInterceptableVersion", + BindingFlags.Static | BindingFlags.NonPublic); + + Assert.NotNull(method); + + s_interceptorVersion = (int)method.Invoke(null, null); + s_initializedInterceptorVersion = true; + } + + return s_interceptorVersion; + } + private static async Task VerifyAgainstBaselineUsingFile( string filename, string testSourceCode, @@ -112,13 +133,17 @@ private static async Task VerifyAgainstBaselineUsingF "net462" #endif ; + string path = extType is ExtensionClassType.None - ? Path.Combine("Baselines", environmentSubFolder, filename) - : Path.Combine("Baselines", environmentSubFolder, extType.ToString(), filename); + ? Path.Combine("Baselines", environmentSubFolder, "Version" + GetInterceptorVersion().ToString(), filename) + : Path.Combine("Baselines", environmentSubFolder, extType.ToString(), "Version" + GetInterceptorVersion().ToString(), filename); string baseline = LineEndingsHelper.Normalize(File.ReadAllText(path)); string[] expectedLines = baseline.Replace("%VERSION%", typeof(ConfigurationBindingGenerator).Assembly.GetName().Version?.ToString()) .Split(new string[] { Environment.NewLine }, StringSplitOptions.None); + // Normalize the source since the interceptor attribute uses a hash of the text. + testSourceCode = testSourceCode.Replace("\r\n", "\n"); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); result.ValidateDiagnostics(expectedDiags);