1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-09 17:44:48 +09:00

Use Roslyn interceptors feature in binder gen (#90340)

* Use Roslyn interceptors feature in binder gen

* Fix polymorphic issue and address feedback

* Fix source build issue

* Revert changes to options gen

* Fix
This commit is contained in:
Layomi Akinrinade 2023-08-19 09:16:49 -07:00 committed by GitHub
parent a429b0ca4f
commit a5ff66c825
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 1590 additions and 1199 deletions

View file

@ -270,3 +270,5 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL
| Suppression ID | Suppressed Diagnostic ID | Description | | Suppression ID | Suppressed Diagnostic ID | Description |
| :----------------------- | :----------------------- | :---------- | | :----------------------- | :----------------------- | :---------- |
| __`SYSLIBSUPPRESS0001`__ | CA1822 | Do not offer to make methods static when the methods need to be instance methods for a custom marshaller shape. | | __`SYSLIBSUPPRESS0001`__ | CA1822 | Do not offer to make methods static when the methods need to be instance methods for a custom marshaller shape. |
| __`SYSLIBSUPPRESS0002`__ | IL2026 | ConfigurationBindingGenerator: suppress RequiresUnreferencedCode diagnostic for binding call that has been intercepted by a generated static variant. |
| __`SYSLIBSUPPRESS0003`__ | IL3050 | ConfigurationBindingGenerator: suppress RequiresDynamicCode diagnostic for binding call that has been intercepted by a generated static variant. |

View file

@ -10,6 +10,7 @@
<UsagePattern IdentityGlob="System.Composition*/*7.*" /> <UsagePattern IdentityGlob="System.Composition*/*7.*" />
<UsagePattern IdentityGlob="Microsoft.CodeAnalysis*/*4.4.*" /> <UsagePattern IdentityGlob="Microsoft.CodeAnalysis*/*4.4.*" />
<UsagePattern IdentityGlob="Microsoft.CodeAnalysis*/*4.5.*" /> <UsagePattern IdentityGlob="Microsoft.CodeAnalysis*/*4.5.*" />
<UsagePattern IdentityGlob="Microsoft.CodeAnalysis*/*4.7.*" />
<!-- Allowed and pinned to major version due to https://github.com/dotnet/source-build/issues/3228 --> <!-- Allowed and pinned to major version due to https://github.com/dotnet/source-build/issues/3228 -->
<UsagePattern IdentityGlob="Microsoft.NETCore.App.Crossgen2.linux-x64/*8.*" /> <UsagePattern IdentityGlob="Microsoft.NETCore.App.Crossgen2.linux-x64/*8.*" />

View file

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using System; using System;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@ -17,7 +18,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
private readonly SourceGenerationSpec _sourceGenSpec; private readonly SourceGenerationSpec _sourceGenSpec;
private bool _emitBlankLineBeforeNextStatement; private bool _emitBlankLineBeforeNextStatement;
private bool _useFullyQualifiedNames;
private int _valueSuffixIndex; private int _valueSuffixIndex;
private static readonly Regex s_arrayBracketsRegex = new(Regex.Escape("[]")); private static readonly Regex s_arrayBracketsRegex = new(Regex.Escape("[]"));
@ -32,7 +32,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public void Emit() public void Emit()
{ {
if (!ShouldEmitBinders()) if (!ShouldEmitBindingExtensions())
{ {
return; return;
} }
@ -42,17 +42,26 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
"""); """);
EmitInterceptsLocationAttrDecl();
EmitStartBlock($"namespace {ProjectName}");
EmitUsingStatements();
_writer.WriteLine(); _writer.WriteLine();
EmitStartBlock($$"""
{{Expression.GeneratedCodeAnnotation}}
file static class {{Identifier.BindingExtensions}}
""");
EmitBindingExtensions_IConfiguration();
EmitBindingExtensions_OptionsBuilder();
EmitBindingExtensions_IServiceCollection();
EmitCoreBindingHelpers();
EmitEndBlock(); // BindingExtensions class
_useFullyQualifiedNames = true; EmitEndBlock(); // Binding namespace.
EmitBinder_Extensions_IConfiguration();
EmitBinder_Extensions_OptionsBuilder();
EmitBinder_Extensions_IServiceCollection();
_useFullyQualifiedNames = false; _context.AddSource($"{Identifier.BindingExtensions}.g.cs", _writer.ToSourceText());
Emit_CoreBindingHelper();
_context.AddSource($"{Identifier.GeneratedConfigurationBinder}.g.cs", _writer.ToSourceText());
} }
private void EmitBindCoreCall( private void EmitBindCoreCall(
@ -74,7 +83,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (initKind is InitializationKind.AssignmentWithNullCheck) if (initKind is InitializationKind.AssignmentWithNullCheck)
{ {
Debug.Assert(!type.IsValueType); Debug.Assert(!type.IsValueType);
_writer.WriteLine($"{type.MinimalDisplayString}? {tempIdentifier} = {memberAccessExpr};"); _writer.WriteLine($"{type.DisplayString}? {tempIdentifier} = {memberAccessExpr};");
EmitBindCoreCall(tempIdentifier, InitializationKind.AssignmentWithNullCheck); EmitBindCoreCall(tempIdentifier, InitializationKind.AssignmentWithNullCheck);
} }
else if (initKind is InitializationKind.None && type.IsValueType) else if (initKind is InitializationKind.None && type.IsValueType)
@ -89,9 +98,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
void EmitBindCoreCall(string objExpression, InitializationKind initKind) void EmitBindCoreCall(string objExpression, InitializationKind initKind)
{ {
string methodDisplayString = GetHelperMethodDisplayString(nameof(MethodsToGen_CoreBindingHelper.BindCore)); string bindCoreCall = $@"{nameof(MethodsToGen_CoreBindingHelper.BindCore)}({configArgExpr}, ref {objExpression}, {Identifier.binderOptions});";
string bindCoreCall = $@"{methodDisplayString}({configArgExpr}, ref {objExpression}, {Identifier.binderOptions});";
EmitObjectInit(objExpression, initKind); EmitObjectInit(objExpression, initKind);
_writer.WriteLine(bindCoreCall); _writer.WriteLine(bindCoreCall);
writeOnSuccess?.Invoke(objExpression); writeOnSuccess?.Invoke(objExpression);
@ -127,12 +134,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
else if (typeKind is StringParsableTypeKind.Enum) else if (typeKind is StringParsableTypeKind.Enum)
{ {
parsedValueExpr = $"ParseEnum<{type.MinimalDisplayString}>({stringValueToParse_Expr}, () => {sectionPathExpr})"; parsedValueExpr = $"ParseEnum<{type.DisplayString}>({stringValueToParse_Expr}, () => {sectionPathExpr})";
} }
else else
{ {
string helperMethodDisplayString = GetHelperMethodDisplayString(type.ParseMethodName); parsedValueExpr = $"{type.ParseMethodName}({stringValueToParse_Expr}, () => {sectionPathExpr})";
parsedValueExpr = $"{helperMethodDisplayString}({stringValueToParse_Expr}, () => {sectionPathExpr})";
} }
if (!checkForNullSectionValue) if (!checkForNullSectionValue)
@ -156,7 +162,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
string initExpr; string initExpr;
CollectionSpec? collectionType = type as CollectionSpec; CollectionSpec? collectionType = type as CollectionSpec;
string effectiveDisplayString = GetTypeDisplayString(type); string effectiveDisplayString = type.DisplayString;
if (collectionType is not null) if (collectionType is not null)
{ {
if (collectionType is EnumerableSpec { InitializationStrategy: InitializationStrategy.Array }) if (collectionType is EnumerableSpec { InitializationStrategy: InitializationStrategy.Array })
@ -165,7 +171,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
else else
{ {
effectiveDisplayString = GetTypeDisplayString(collectionType.ConcreteType ?? collectionType); effectiveDisplayString = (collectionType.ConcreteType ?? collectionType).DisplayString;
initExpr = $"new {effectiveDisplayString}()"; initExpr = $"new {effectiveDisplayString}()";
} }
} }
@ -215,36 +221,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return true; return true;
} }
private void EmitCastToIConfigurationSection() private void EmitInterceptsLocationAttrDecl()
{ {
string sectionTypeDisplayString; _writer.WriteLine();
string exceptionTypeDisplayString;
if (_useFullyQualifiedNames)
{
sectionTypeDisplayString = "global::Microsoft.Extensions.Configuration.IConfigurationSection";
exceptionTypeDisplayString = FullyQualifiedDisplayString.InvalidOperationException;
}
else
{
sectionTypeDisplayString = Identifier.IConfigurationSection;
exceptionTypeDisplayString = nameof(InvalidOperationException);
}
_writer.WriteLine($$""" _writer.WriteLine($$"""
if ({{Identifier.configuration}} is not {{sectionTypeDisplayString}} {{Identifier.section}}) namespace System.Runtime.CompilerServices
{ {
throw new {{exceptionTypeDisplayString}}(); using System;
using System.CodeDom.Compiler;
{{Expression.GeneratedCodeAnnotation}}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string filePath, int line, int column)
{
}
}
} }
"""); """);
_writer.WriteLine();
}
private void EmitUsingStatements()
{
foreach (string @namespace in _sourceGenSpec.Namespaces.ToImmutableSortedSet())
{
_writer.WriteLine($"using {@namespace};");
}
} }
private void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn) private void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn)
{ {
string returnPostfix = voidReturn ? string.Empty : " null"; string returnPostfix = voidReturn ? string.Empty : " null";
string methodDisplayString = GetHelperMethodDisplayString(Identifier.HasValueOrChildren);
_writer.WriteLine($$""" _writer.WriteLine($$"""
if (!{{methodDisplayString}}({{Identifier.configuration}})) if (!{{Identifier.HasValueOrChildren}}({{Identifier.configuration}}))
{ {
return{{returnPostfix}}; return{{returnPostfix}};
} }

View file

@ -1,12 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
@ -147,7 +149,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
// List<string> is used in generated code as a temp holder for formatting // List<string> is used in generated code as a temp holder for formatting
// an error for config properties that don't map to object properties. // an error for config properties that don't map to object properties.
_sourceGenSpec.TypeNamespaces.Add("System.Collections.Generic"); _sourceGenSpec.Namespaces.Add("System.Collections.Generic");
spec = CreateObjectSpec(namedType); spec = CreateObjectSpec(namedType);
} }
@ -169,12 +171,36 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
string @namespace = spec.Namespace; string @namespace = spec.Namespace;
if (@namespace is not null and not "<global namespace>") if (@namespace is not null and not "<global namespace>")
{ {
_sourceGenSpec.TypeNamespaces.Add(@namespace); _sourceGenSpec.Namespaces.Add(@namespace);
} }
return _createdSpecs[type] = spec; return _createdSpecs[type] = spec;
} }
private void RegisterTypeForBindCoreMainGen(TypeSpec typeSpec)
{
if (typeSpec.NeedsMemberBinding)
{
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreMain, typeSpec);
RegisterTypeForBindCoreGen(typeSpec);
_sourceGenSpec.MethodsToGen_CoreBindingHelper |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren;
}
}
private void RegisterTypeForBindCoreGen(TypeSpec typeSpec)
{
if (typeSpec.NeedsMemberBinding)
{
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, typeSpec);
}
}
private void RegisterTypeForGetCoreGen(TypeSpec typeSpec)
{
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, typeSpec);
_sourceGenSpec.MethodsToGen_CoreBindingHelper |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren;
}
private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type) private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type)
{ {
if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(method, out HashSet<TypeSpec>? types)) if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(method, out HashSet<TypeSpec>? types))
@ -186,14 +212,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
_sourceGenSpec.MethodsToGen_CoreBindingHelper |= method; _sourceGenSpec.MethodsToGen_CoreBindingHelper |= method;
} }
private void RegisterTypeForBindCoreUntypedGen(TypeSpec typeSpec) /// <summary>
{ /// Registers interceptors for root binding methods, except for ConfigurationBinder.Bind,
if (typeSpec.NeedsMemberBinding) /// which is handled by <see cref="RegisterAsInterceptor_ConfigBinder_BindMethod"/>
{ /// </summary>
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, typeSpec); private void RegisterAsInterceptor(Enum method, IInvocationOperation operation) =>
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreUntyped, typeSpec); _sourceGenSpec.InterceptionInfo.RegisterCacheEntry(method, new InterceptorLocationInfo(operation));
}
}
private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType) private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType)
{ {
@ -335,7 +359,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
// We want a BindCore method for List<TElement> as a temp holder for the array values. We know the element type is supported. // We want a BindCore method for List<TElement> as a temp holder for the array values. We know the element type is supported.
EnumerableSpec listSpec = (GetOrCreateTypeSpec(_typeSymbols.List.Construct(arrayType.ElementType)) as EnumerableSpec)!; EnumerableSpec listSpec = (GetOrCreateTypeSpec(_typeSymbols.List.Construct(arrayType.ElementType)) as EnumerableSpec)!;
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, listSpec); RegisterTypeForBindCoreGen(listSpec);
EnumerableSpec spec = new EnumerableSpec(arrayType) EnumerableSpec spec = new EnumerableSpec(arrayType)
{ {
@ -347,7 +371,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
}; };
Debug.Assert(spec.CanInitialize); Debug.Assert(spec.CanInitialize);
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec); RegisterTypeForBindCoreGen(spec);
return spec; return spec;
} }
@ -383,7 +407,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (spec is not null) if (spec is not null)
{ {
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec); RegisterTypeForBindCoreGen(spec);
spec.InitExceptionMessage ??= spec.ElementType.InitExceptionMessage; spec.InitExceptionMessage ??= spec.ElementType.InitExceptionMessage;
} }
@ -442,7 +466,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
constructionStrategy = InitializationStrategy.ToEnumerableMethod; constructionStrategy = InitializationStrategy.ToEnumerableMethod;
populationStrategy = CollectionPopulationStrategy.Cast_Then_Add; populationStrategy = CollectionPopulationStrategy.Cast_Then_Add;
toEnumerableMethodCall = "ToDictionary(pair => pair.Key, pair => pair.Value)"; toEnumerableMethodCall = "ToDictionary(pair => pair.Key, pair => pair.Value)";
_sourceGenSpec.TypeNamespaces.Add("System.Linq"); _sourceGenSpec.Namespaces.Add("System.Linq");
} }
else else
{ {
@ -711,7 +735,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (objectSpec.NeedsMemberBinding) if (objectSpec.NeedsMemberBinding)
{ {
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, objectSpec); RegisterTypeForBindCoreGen(objectSpec);
} }
return objectSpec; return objectSpec;
@ -890,4 +914,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
} }
public static class ParserExtensions
{
public static void RegisterCacheEntry<TKey, TValue, TEntry>(this Dictionary<TKey, TValue> cache, TKey key, TEntry entry)
where TKey : notnull
where TValue : ICollection<TEntry>, new()
{
if (!cache.TryGetValue(key, out TValue? entryCollection))
{
cache[key] = entryCollection = new TValue();
}
entryCollection.Add(entry);
}
}
} }

View file

@ -0,0 +1,70 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
public sealed partial class ConfigurationBindingGenerator
{
/// <summary>
/// Supresses false-positive diagnostics emitted by the linker
/// when analyzing binding invocations that we have intercepted.
/// Workaround for https://github.com/dotnet/roslyn/issues/68669.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class Suppressor : DiagnosticSuppressor
{
private const string Justification = "The target method has been intercepted by a generated static variant.";
/// <summary>
/// Suppression descriptor for IL2026: Members attributed with RequiresUnreferencedCode may break when trimming.
/// </summary>
private static readonly SuppressionDescriptor RUCDiagnostic = new(id: "SYSLIBSUPPRESS0002", suppressedDiagnosticId: "IL2026", Justification);
/// <summary>
/// Suppression descriptor for IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as native AOT.
/// </summary>
private static readonly SuppressionDescriptor RDCDiagnostic = new(id: "SYSLIBSUPPRESS0003", suppressedDiagnosticId: "IL3050", Justification);
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(RUCDiagnostic, RDCDiagnostic);
public override void ReportSuppressions(SuppressionAnalysisContext context)
{
foreach (Diagnostic diagnostic in context.ReportedDiagnostics)
{
string diagnosticId = diagnostic.Id;
if (diagnosticId != RDCDiagnostic.SuppressedDiagnosticId && diagnosticId != RUCDiagnostic.SuppressedDiagnosticId)
{
continue;
}
Location location = diagnostic.AdditionalLocations.Count > 0
? diagnostic.AdditionalLocations[0]
: diagnostic.Location;
bool shouldSuppressDiagnostic =
location.SourceTree is SyntaxTree sourceTree &&
sourceTree.GetRoot().FindNode(location.SourceSpan) is SyntaxNode syntaxNode &&
BinderInvocation.IsCandidateSyntaxNode(syntaxNode) &&
context.GetSemanticModel(sourceTree)
.GetOperation((InvocationExpressionSyntax)syntaxNode, context.CancellationToken) is IInvocationOperation operation &&
BinderInvocation.IsBindingOperation(operation);
if (shouldSuppressDiagnostic)
{
SuppressionDescriptor targetSuppression = diagnosticId == RUCDiagnostic.SuppressedDiagnosticId
? RUCDiagnostic
: RDCDiagnostic;
context.ReportSuppression(Suppression.Create(targetSuppression, diagnostic));
}
}
}
}
}
}

View file

@ -5,7 +5,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
@ -15,7 +14,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
[Generator] [Generator]
public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerator public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerator
{ {
internal const string ProjectName = "Microsoft.Extensions.Configuration.Binder.SourceGeneration"; private static readonly string ProjectName = Emitter.s_assemblyName.Name;
public void Initialize(IncrementalGeneratorInitializationContext context) public void Initialize(IncrementalGeneratorInitializationContext context)
{ {
@ -42,9 +41,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
context.RegisterSourceOutput(inputData, (spc, source) => Execute(source.Item1, source.Item2, spc)); context.RegisterSourceOutput(inputData, (spc, source) => Execute(source.Item1, source.Item2, spc));
} }
/// <summary>
/// Generates source code to optimize binding with ConfigurationBinder.
/// </summary>
private static void Execute(CompilationData compilationData, ImmutableArray<BinderInvocation> inputCalls, SourceProductionContext context) private static void Execute(CompilationData compilationData, ImmutableArray<BinderInvocation> inputCalls, SourceProductionContext context)
{ {
if (inputCalls.IsDefaultOrEmpty) if (inputCalls.IsDefaultOrEmpty)
@ -73,7 +69,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public CompilationData(CSharpCompilation compilation) public CompilationData(CSharpCompilation compilation)
{ {
LanguageVersionIsSupported = compilation.LanguageVersion >= LanguageVersion.CSharp11; // We don't have a CSharp21 value available yet. Polyfill the value here for forward compat, rather than use the LangugeVersion.Preview enum value.
// https://github.com/dotnet/roslyn/blob/168689931cb4e3150641ec2fb188a64ce4b3b790/src/Compilers/CSharp/Portable/LanguageVersion.cs#L218-L232
const int LangVersion_CSharp12 = 1200;
LanguageVersionIsSupported = (int)compilation.LanguageVersion >= LangVersion_CSharp12;
if (LanguageVersionIsSupported) if (LanguageVersionIsSupported)
{ {
TypeSymbols = new KnownTypeSymbols(compilation); TypeSymbols = new KnownTypeSymbols(compilation);

View file

@ -2,9 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using SourceGenerators;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
@ -14,91 +12,84 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
private bool ShouldEmitMethods(MethodsToGen_ConfigurationBinder methods) => (_sourceGenSpec.MethodsToGen_ConfigurationBinder & methods) != 0; private bool ShouldEmitMethods(MethodsToGen_ConfigurationBinder methods) => (_sourceGenSpec.MethodsToGen_ConfigurationBinder & methods) != 0;
private void EmitBinder_Extensions_IConfiguration() private void EmitBindingExtensions_IConfiguration()
{ {
Debug.Assert(_sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods.Count <= 3 &&
!_sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods.Keys.Any(overload => (overload & MethodsToGen_ConfigurationBinder.Bind) is 0));
if (!ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Any)) if (!ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Any))
{ {
return; return;
} }
_emitBlankLineBeforeNextStatement = false; EmitBindingExtStartRegion(Identifier.IConfiguration);
EmitRootBindingClassStartBlock(Identifier.GeneratedConfigurationBinder);
EmitGetMethods(); EmitGetMethods();
EmitGetValueMethods(); EmitGetValueMethods();
EmitBindMethods_ConfigurationBinder(); EmitBindMethods_ConfigurationBinder();
EmitBindingExtEndRegion();
EmitEndBlock();
_emitBlankLineBeforeNextStatement = true;
} }
private void EmitGetMethods() private void EmitGetMethods()
{ {
const string expressionForGetCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.GetCore)}"; const string expressionForGetCore = nameof(MethodsToGen_CoreBindingHelper.GetCore);
const string documentation = "Attempts to bind the configuration instance to a new instance of type T."; const string documentation = "Attempts to bind the configuration instance to a new instance of type T.";
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_T)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_T))
{ {
StartMethodDefinition(documentation); StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_T, documentation);
_writer.WriteLine($"public static T? {Identifier.Get}<T>(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}) => " + _writer.WriteLine($"public static T? {Identifier.Get}<T>(this {Identifier.IConfiguration} {Identifier.configuration}) => " +
$"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureOptions}: null) ?? default(T));"); $"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureOptions}: null) ?? default(T));");
} }
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_T_BinderOptions)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_T_BinderOptions))
{ {
StartMethodDefinition(documentation); StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_T_BinderOptions, documentation);
_writer.WriteLine($"public static T? {Identifier.Get}<T>(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}) => " + _writer.WriteLine($"public static T? {Identifier.Get}<T>(this {Identifier.IConfiguration} {Identifier.configuration}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}) => " +
$"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureOptions}) ?? default(T));"); $"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureOptions}) ?? default(T));");
} }
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_TypeOf)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_TypeOf))
{ {
StartMethodDefinition(documentation); StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_TypeOf, documentation);
_writer.WriteLine($"public static object? {Identifier.Get}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.Type} {Identifier.type}) => " + _writer.WriteLine($"public static object? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}) => " +
$"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions}: null);"); $"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions}: null);");
} }
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_TypeOf_BinderOptions)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_TypeOf_BinderOptions))
{ {
StartMethodDefinition(documentation); StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_TypeOf_BinderOptions, documentation);
_writer.WriteLine($"public static object? {Identifier.Get}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.Type} {Identifier.type}, {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}) => " + _writer.WriteLine($"public static object? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}) => " +
$"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions});"); $"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions});");
} }
} }
private void EmitGetValueMethods() private void EmitGetValueMethods()
{ {
const string expressionForGetValueCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}"; const string expressionForGetValueCore = $"{Identifier.BindingExtensions}.{nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}";
const string documentation = "Extracts the value with the specified key and converts it to the specified type."; const string documentation = "Extracts the value with the specified key and converts it to the specified type.";
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_T_key)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_T_key))
{ {
StartMethodDefinition(documentation); StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_T_key, documentation);
_writer.WriteLine($"public static T? {Identifier.GetValue}<T>(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, string {Identifier.key}) => " + _writer.WriteLine($"public static T? {Identifier.GetValue}<T>(this {Identifier.IConfiguration} {Identifier.configuration}, string {Identifier.key}) => " +
$"(T?)({expressionForGetValueCore}({Identifier.configuration}, typeof(T), {Identifier.key}) ?? default(T));"); $"(T?)({expressionForGetValueCore}({Identifier.configuration}, typeof(T), {Identifier.key}) ?? default(T));");
} }
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_T_key_defaultValue)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_T_key_defaultValue))
{ {
StartMethodDefinition(documentation); StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_T_key_defaultValue, documentation);
_writer.WriteLine($"public static T? {Identifier.GetValue}<T>(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, string {Identifier.key}, T {Identifier.defaultValue}) => " + _writer.WriteLine($"public static T? {Identifier.GetValue}<T>(this {Identifier.IConfiguration} {Identifier.configuration}, string {Identifier.key}, T {Identifier.defaultValue}) => " +
$"(T?)({expressionForGetValueCore}({Identifier.configuration}, typeof(T), {Identifier.key}) ?? {Identifier.defaultValue});"); $"(T?)({expressionForGetValueCore}({Identifier.configuration}, typeof(T), {Identifier.key}) ?? {Identifier.defaultValue});");
} }
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key))
{ {
StartMethodDefinition(documentation); StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key, documentation);
_writer.WriteLine($"public static object? {Identifier.GetValue}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.Type} {Identifier.type}, string {Identifier.key}) => " + _writer.WriteLine($"public static object? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key}) => " +
$"{expressionForGetValueCore}({Identifier.configuration}, {Identifier.type}, {Identifier.key});"); $"{expressionForGetValueCore}({Identifier.configuration}, {Identifier.type}, {Identifier.key});");
} }
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key_defaultValue)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key_defaultValue))
{ {
StartMethodDefinition(documentation); StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key_defaultValue, documentation);
_writer.WriteLine($"public static object? {Identifier.GetValue}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.Type} {Identifier.type}, string {Identifier.key}, object? {Identifier.defaultValue}) => " + _writer.WriteLine($"public static object? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key}, object? {Identifier.defaultValue}) => " +
$"{expressionForGetValueCore}({Identifier.configuration}, {Identifier.type}, {Identifier.key}) ?? {Identifier.defaultValue};"); $"{expressionForGetValueCore}({Identifier.configuration}, {Identifier.type}, {Identifier.key}) ?? {Identifier.defaultValue};");
} }
} }
@ -110,72 +101,71 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
Dictionary<MethodsToGen_ConfigurationBinder, HashSet<TypeSpec>> types = _sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods; string objParamExpr = $"object? {Identifier.obj}";
if (types.TryGetValue(MethodsToGen_ConfigurationBinder.Bind_instance, out HashSet<TypeSpec>? typeSpecs)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance))
{ {
foreach (TypeSpec type in typeSpecs) EmitMethods(
{ MethodsToGen_ConfigurationBinder.Bind_instance,
EmitMethodImplementation( additionalParams: objParamExpr,
type, configExpression: Identifier.configuration,
additionalParams: GetObjParameter(type), configureOptions: false);
configExpression: Identifier.configuration,
configureOptions: false);
}
} }
if (types.TryGetValue(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions, out typeSpecs)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions))
{ {
foreach (TypeSpec type in typeSpecs) EmitMethods(
{ MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions,
EmitMethodImplementation( additionalParams: $"{objParamExpr}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}",
type, configExpression: Identifier.configuration,
additionalParams: $"{GetObjParameter(type)}, {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}", configureOptions: true);
configExpression: Identifier.configuration,
configureOptions: true);
}
} }
if (types.TryGetValue(MethodsToGen_ConfigurationBinder.Bind_key_instance, out typeSpecs)) if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_key_instance))
{ {
foreach (TypeSpec type in typeSpecs) EmitMethods(
{ MethodsToGen_ConfigurationBinder.Bind_key_instance,
EmitMethodImplementation( additionalParams: $"string {Identifier.key}, {objParamExpr}",
type, configExpression: $"{Expression.configurationGetSection}({Identifier.key})",
additionalParams: $"string {Identifier.key}, {GetObjParameter(type)}", configureOptions: false);
configExpression: $"{Expression.configurationGetSection}({Identifier.key})",
configureOptions: false);
}
} }
void EmitMethodImplementation(TypeSpec type, string additionalParams, string configExpression, bool configureOptions) void EmitMethods(MethodsToGen_ConfigurationBinder method, string additionalParams, string configExpression, bool configureOptions)
{ {
string binderOptionsArg = configureOptions ? $"{Expression.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null"; foreach (KeyValuePair<TypeSpec, List<InterceptorLocationInfo>> pair in _sourceGenSpec.InterceptionInfo_ConfigBinder.GetOverloadInfo(method))
string returnExpression;
if (type.CanInitialize)
{ {
returnExpression = type.NeedsMemberBinding (TypeSpec type, List<InterceptorLocationInfo> interceptorInfoList) = (pair.Key, pair.Value);
? $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.BindCore)}({configExpression}, ref {Identifier.obj}, {binderOptionsArg})"
: "{ }";
}
else
{
returnExpression = GetInitException(type.InitExceptionMessage);
}
StartMethodDefinition("Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively."); EmitBlankLineIfRequired();
_writer.WriteLine($"public static void {Identifier.Bind}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {additionalParams}) => " _writer.WriteLine($"/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>");
+ $"{returnExpression};"); EmitInterceptsLocationAnnotations(interceptorInfoList);
EmitStartBlock($"public static void {Identifier.Bind}_{type.DisplayString.ToIdentifierSubstring()}(this {Identifier.IConfiguration} {Identifier.configuration}, {additionalParams})");
if (!EmitInitException(type) && type.NeedsMemberBinding)
{
string binderOptionsArg = configureOptions ? $"{Identifier.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null";
EmitCheckForNullArgument_WithBlankLine(Identifier.configuration);
if (!type.IsValueType)
{
EmitCheckForNullArgument_WithBlankLine(Identifier.obj);
}
_writer.WriteLine($$"""
var {{Identifier.typedObj}} = ({{type.EffectiveType.DisplayString}}){{Identifier.obj}};
{{nameof(MethodsToGen_CoreBindingHelper.BindCore)}}({{configExpression}}, ref {{Identifier.typedObj}}, {{binderOptionsArg}});
""");
}
EmitEndBlock();
}
} }
string GetObjParameter(TypeSpec type) => $"{type.FullyQualifiedDisplayString} {Identifier.obj}";
} }
private void StartMethodDefinition(string documentation) private void StartMethodDefinition(MethodsToGen_ConfigurationBinder method, string documentation)
{ {
EmitBlankLineIfRequired(); EmitBlankLineIfRequired();
_writer.WriteLine($"/// <summary>{documentation}</summary>"); _writer.WriteLine($"/// <summary>{documentation}</summary>");
EmitInterceptsLocationAnnotations(method);
} }
} }
} }

View file

@ -3,10 +3,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using SourceGenerators;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
@ -16,41 +16,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
private bool ShouldEmitMethods(MethodsToGen_CoreBindingHelper methods) => (_sourceGenSpec.MethodsToGen_CoreBindingHelper & methods) != 0; private bool ShouldEmitMethods(MethodsToGen_CoreBindingHelper methods) => (_sourceGenSpec.MethodsToGen_CoreBindingHelper & methods) != 0;
private void Emit_CoreBindingHelper() private void EmitCoreBindingHelpers()
{ {
Debug.Assert(_emitBlankLineBeforeNextStatement); Debug.Assert(_emitBlankLineBeforeNextStatement);
_writer.WriteLine(); EmitBindingExtStartRegion("Core binding");
_emitBlankLineBeforeNextStatement = false;
EmitStartBlock($"namespace {ProjectName}");
EmitHelperUsingStatements();
_writer.WriteLine();
EmitStartBlock($$"""
/// <summary>Provide core binding logic.</summary>
{{GetGeneratedCodeAttributeSrc()}}
file static class {{Identifier.CoreBindingHelper}}
""");
EmitConfigurationKeyCaches(); EmitConfigurationKeyCaches();
EmitGetCoreMethod(); EmitGetCoreMethod();
EmitGetValueCoreMethod(); EmitGetValueCoreMethod();
EmitBindCoreUntypedMethod(); EmitBindCoreMainMethod();
EmitBindCoreMethods(); EmitBindCoreMethods();
EmitInitializeMethods(); EmitInitializeMethods();
EmitHelperMethods(); EmitHelperMethods();
EmitBindingExtEndRegion();
EmitEndBlock(); // End helper class.
EmitEndBlock(); // End namespace.
}
private void EmitHelperUsingStatements()
{
foreach (string @namespace in _sourceGenSpec.TypeNamespaces.ToImmutableSortedSet())
{
_writer.WriteLine($"using {@namespace};");
}
} }
private void EmitConfigurationKeyCaches() private void EmitConfigurationKeyCaches()
@ -60,6 +37,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
EmitBlankLineIfRequired();
foreach (TypeSpec type in targetTypes) foreach (TypeSpec type in targetTypes)
{ {
if (type is not ObjectSpec objectType) if (type is not ObjectSpec objectType)
@ -73,10 +52,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
string configKeysSource = string.Join(", ", keys); string configKeysSource = string.Join(", ", keys);
string fieldName = GetConfigKeyCacheFieldName(objectType); string fieldName = GetConfigKeyCacheFieldName(objectType);
_writer.WriteLine($@"private readonly static Lazy<{MinimalDisplayString.HashSetOfString}> {fieldName} = new(() => new {MinimalDisplayString.HashSetOfString}(StringComparer.OrdinalIgnoreCase) {{ {configKeysSource} }});"); _writer.WriteLine($@"private readonly static Lazy<{TypeDisplayString.HashSetOfString}> {fieldName} = new(() => new {TypeDisplayString.HashSetOfString}(StringComparer.OrdinalIgnoreCase) {{ {configKeysSource} }});");
} }
_emitBlankLineBeforeNextStatement = true;
} }
private void EmitGetCoreMethod() private void EmitGetCoreMethod()
@ -96,16 +73,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
EmitIConfigurationHasValueOrChildrenCheck(voidReturn: false); EmitIConfigurationHasValueOrChildrenCheck(voidReturn: false);
bool isFirstType = true;
foreach (TypeSpec type in types) foreach (TypeSpec type in types)
{ {
TypeSpec effectiveType = type.EffectiveType; TypeSpec effectiveType = type.EffectiveType;
TypeSpecKind kind = effectiveType.SpecKind; TypeSpecKind kind = effectiveType.SpecKind;
string conditionKindExpr = GetConditionKindExpr(ref isFirstType);
EmitStartBlock($"if (type == typeof({type.MinimalDisplayString}))"); EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))");
if (effectiveType is ParsableFromStringSpec stringParsableType) if (effectiveType is ParsableFromStringSpec stringParsableType)
{ {
EmitCastToIConfigurationSection(); _writer.WriteLine($$"""
if ({{Identifier.configuration}} is not {{Identifier.IConfigurationSection}} {{Identifier.section}})
{
throw new {{Identifier.InvalidOperationException}}();
}
""");
EmitBindLogicFromString( EmitBindLogicFromString(
stringParsableType, stringParsableType,
Expression.sectionValue, Expression.sectionValue,
@ -121,9 +106,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
EmitEndBlock(); EmitEndBlock();
_writer.WriteLine();
} }
_writer.WriteLine();
Emit_NotSupportedException_TypeNotDetectedAsInput(); Emit_NotSupportedException_TypeNotDetectedAsInput();
EmitEndBlock(); EmitEndBlock();
_emitBlankLineBeforeNextStatement = true; _emitBlankLineBeforeNextStatement = true;
@ -152,9 +137,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
_writer.WriteLine(); _writer.WriteLine();
bool isFirstType = true;
foreach (TypeSpec type in targetTypes) foreach (TypeSpec type in targetTypes)
{ {
EmitStartBlock($"if ({Identifier.type} == typeof({type.MinimalDisplayString}))"); string conditionKindExpr = GetConditionKindExpr(ref isFirstType);
EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))");
EmitBindLogicFromString( EmitBindLogicFromString(
(ParsableFromStringSpec)type.EffectiveType, (ParsableFromStringSpec)type.EffectiveType,
@ -165,51 +152,48 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
useIncrementalStringValueIdentifier: false); useIncrementalStringValueIdentifier: false);
EmitEndBlock(); EmitEndBlock();
_writer.WriteLine();
} }
_writer.WriteLine();
_writer.WriteLine("return null;"); _writer.WriteLine("return null;");
EmitEndBlock(); EmitEndBlock();
_emitBlankLineBeforeNextStatement = true; _emitBlankLineBeforeNextStatement = true;
} }
private void EmitBindCoreUntypedMethod() private void EmitBindCoreMainMethod()
{ {
if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.BindCoreUntyped, out HashSet<TypeSpec>? targetTypes)) if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.BindCoreMain, out HashSet<TypeSpec>? targetTypes))
{ {
return; return;
} }
EmitBlankLineIfRequired(); EmitBlankLineIfRequired();
EmitStartBlock($"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCoreMain)}({Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.obj}, Type {Identifier.type}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})");
EmitStartBlock($"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)}(this {Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.obj}, Type {Identifier.type}, {MinimalDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})");
EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); EmitCheckForNullArgument_WithBlankLine(Identifier.configuration);
EmitCheckForNullArgument_WithBlankLine(Identifier.obj);
EmitIConfigurationHasValueOrChildrenCheck(voidReturn: true);
_writer.WriteLine($"{Identifier.BinderOptions}? {Identifier.binderOptions} = {Identifier.GetBinderOptions}({Identifier.configureOptions});"); _writer.WriteLine($"{Identifier.BinderOptions}? {Identifier.binderOptions} = {Identifier.GetBinderOptions}({Identifier.configureOptions});");
_writer.WriteLine(); _writer.WriteLine();
EmitIConfigurationHasValueOrChildrenCheck(voidReturn: true); bool isFirstType = true;
foreach (TypeSpec type in targetTypes) foreach (TypeSpec type in targetTypes)
{ {
EmitStartBlock($"if (type == typeof({type.MinimalDisplayString}))");
TypeSpec effectiveType = type.EffectiveType; TypeSpec effectiveType = type.EffectiveType;
string conditionKindExpr = GetConditionKindExpr(ref isFirstType);
EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))");
if (!EmitInitException(effectiveType)) if (!EmitInitException(effectiveType))
{ {
_writer.WriteLine($"var {Identifier.temp} = ({effectiveType.MinimalDisplayString}){Identifier.obj};"); _writer.WriteLine($"var {Identifier.temp} = ({effectiveType.DisplayString}){Identifier.obj};");
EmitBindCoreCall(type, Identifier.temp, Identifier.configuration, InitializationKind.None); EmitBindCoreCall(type, Identifier.temp, Identifier.configuration, InitializationKind.None);
_writer.WriteLine($"return;"); _writer.WriteLine($"return;");
} }
EmitEndBlock(); EmitEndBlock();
_writer.WriteLine();
} }
_writer.WriteLine();
Emit_NotSupportedException_TypeNotDetectedAsInput(); Emit_NotSupportedException_TypeNotDetectedAsInput();
EmitEndBlock(); EmitEndBlock();
_emitBlankLineBeforeNextStatement = true;
} }
private void EmitBindCoreMethods() private void EmitBindCoreMethods()
@ -231,11 +215,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
Debug.Assert(type.CanInitialize); Debug.Assert(type.CanInitialize);
string objParameterExpression = $"ref {type.MinimalDisplayString} {Identifier.obj}"; string objParameterExpression = $"ref {type.DisplayString} {Identifier.obj}";
EmitStartBlock(@$"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCore)}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); EmitStartBlock(@$"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCore)}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, {Identifier.BinderOptions}? {Identifier.binderOptions})");
EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);
TypeSpec effectiveType = type.EffectiveType; TypeSpec effectiveType = type.EffectiveType;
if (effectiveType is EnumerableSpec enumerable) if (effectiveType is EnumerableSpec enumerable)
{ {
@ -281,9 +263,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
List<ParameterSpec> ctorParams = type.ConstructorParameters; List<ParameterSpec> ctorParams = type.ConstructorParameters;
IEnumerable<PropertySpec> initOnlyProps = type.Properties.Values.Where(prop => prop is { SetOnInit: true }); IEnumerable<PropertySpec> initOnlyProps = type.Properties.Values.Where(prop => prop is { SetOnInit: true });
List<string> ctorArgList = new(); List<string> ctorArgList = new();
string displayString = type.MinimalDisplayString; string displayString = type.DisplayString;
EmitStartBlock($"public static {type.MinimalDisplayString} {GetInitalizeMethodDisplayString(type)}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); EmitStartBlock($"public static {type.DisplayString} {GetInitalizeMethodDisplayString(type)}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})");
_emitBlankLineBeforeNextStatement = false; _emitBlankLineBeforeNextStatement = false;
foreach (ParameterSpec parameter in ctorParams) foreach (ParameterSpec parameter in ctorParams)
@ -335,7 +317,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
TypeSpec memberType = member.Type; TypeSpec memberType = member.Type;
bool errorOnFailedBinding = member.ErrorOnFailedBinding; bool errorOnFailedBinding = member.ErrorOnFailedBinding;
string parsedMemberIdentifierDeclarationPrefix = $"{memberType.MinimalDisplayString} {member.Name}"; string parsedMemberIdentifierDeclarationPrefix = $"{memberType.DisplayString} {member.Name}";
string parsedMemberIdentifier; string parsedMemberIdentifier;
if (memberType is ParsableFromStringSpec { StringParsableTypeKind: StringParsableTypeKind.AssignFromSectionValue }) if (memberType is ParsableFromStringSpec { StringParsableTypeKind: StringParsableTypeKind.AssignFromSectionValue })
@ -344,7 +326,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (errorOnFailedBinding) if (errorOnFailedBinding)
{ {
string condition = $@" if ({Identifier.configuration}[""{member.ConfigurationKeyName}""] is not {memberType.MinimalDisplayString} {member.Name})"; string condition = $@" if ({Identifier.configuration}[""{member.ConfigurationKeyName}""] is not {memberType.DisplayString} {member.Name})";
EmitThrowBlock(condition); EmitThrowBlock(condition);
_writer.WriteLine(); _writer.WriteLine();
return; return;
@ -393,40 +375,42 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
_writer.WriteLine($$""" _writer.WriteLine($$"""
{{condition}} {{condition}}
{ {
throw new {{GetInvalidOperationDisplayName()}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.Name, member.Name)}}"); throw new {{Identifier.InvalidOperationException}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.Name, member.Name)}}");
} }
"""); """);
} }
} }
private void EmitHelperMethods() private void EmitHelperMethods()
{ {
// Emitted if we are to bind objects with complex members, or if we're emitting BindCoreMain or GetCore methods.
bool emitAsConfigWithChildren = ShouldEmitMethods(MethodsToGen_CoreBindingHelper.AsConfigWithChildren);
if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCore)) if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCore))
{ {
EmitBlankLineIfRequired();
EmitValidateConfigurationKeysMethod(); EmitValidateConfigurationKeysMethod();
} }
if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreUntyped | MethodsToGen_CoreBindingHelper.GetCore)) if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreMain | MethodsToGen_CoreBindingHelper.GetCore))
{ {
_writer.WriteLine(); // HasValueOrChildren references this method.
Debug.Assert(emitAsConfigWithChildren);
EmitBlankLineIfRequired();
EmitHasValueOrChildrenMethod(); EmitHasValueOrChildrenMethod();
_writer.WriteLine();
EmitAsConfigWithChildrenMethod();
_emitBlankLineBeforeNextStatement = true;
}
else if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.AsConfigWithChildren))
{
_writer.WriteLine();
EmitAsConfigWithChildrenMethod();
_emitBlankLineBeforeNextStatement = true;
} }
if (ShouldEmitMethods( if (emitAsConfigWithChildren)
MethodsToGen_CoreBindingHelper.BindCoreUntyped | MethodsToGen_CoreBindingHelper.GetCore) || {
EmitBlankLineIfRequired();
EmitAsConfigWithChildrenMethod();
}
if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreMain | MethodsToGen_CoreBindingHelper.GetCore) ||
ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions)) ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions))
{ {
_writer.WriteLine(); EmitBlankLineIfRequired();
EmitGetBinderOptionsHelper(); EmitGetBinderOptionsHelper();
_emitBlankLineBeforeNextStatement = true;
} }
bool enumTypeExists = false; bool enumTypeExists = false;
@ -458,17 +442,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
EmitBlankLineIfRequired(); EmitBlankLineIfRequired();
_writer.WriteLine($$""" _writer.WriteLine($$"""
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void {{Identifier.ValidateConfigurationKeys}}(Type {{Identifier.type}}, {{MinimalDisplayString.LazyHashSetOfString}} {{keysIdentifier}}, {{Identifier.IConfiguration}} {{Identifier.configuration}}, {{Identifier.BinderOptions}}? {{Identifier.binderOptions}}) public static void {{Identifier.ValidateConfigurationKeys}}(Type {{Identifier.type}}, {{TypeDisplayString.LazyHashSetOfString}} {{keysIdentifier}}, {{Identifier.IConfiguration}} {{Identifier.configuration}}, {{Identifier.BinderOptions}}? {{Identifier.binderOptions}})
{ {
if ({{Identifier.binderOptions}}?.{{Identifier.ErrorOnUnknownConfiguration}} is true) if ({{Identifier.binderOptions}}?.{{Identifier.ErrorOnUnknownConfiguration}} is true)
{ {
{{MinimalDisplayString.ListOfString}}? {{Identifier.temp}} = null; {{TypeDisplayString.ListOfString}}? {{Identifier.temp}} = null;
foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}()) foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
{ {
if (!{{keysIdentifier}}.Value.Contains({{Expression.sectionKey}})) if (!{{keysIdentifier}}.Value.Contains({{Expression.sectionKey}}))
{ {
({{Identifier.temp}} ??= new {{MinimalDisplayString.ListOfString}}()).Add($"'{{{Expression.sectionKey}}}'"); ({{Identifier.temp}} ??= new {{TypeDisplayString.ListOfString}}()).Add($"'{{{Expression.sectionKey}}}'");
} }
} }
@ -490,7 +474,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
return true; return true;
} }
return {{Identifier.AsConfigWithChildren}}({{Identifier.configuration}}) is not null; return {{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}}({{Identifier.configuration}}) is not null;
} }
"""); """);
} }
@ -498,7 +482,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
private void EmitAsConfigWithChildrenMethod() private void EmitAsConfigWithChildrenMethod()
{ {
_writer.WriteLine($$""" _writer.WriteLine($$"""
public static {{Identifier.IConfiguration}}? {{Identifier.AsConfigWithChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}}) public static {{Identifier.IConfiguration}}? {{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}})
{ {
foreach ({{Identifier.IConfigurationSection}} _ in {{Identifier.configuration}}.{{Identifier.GetChildren}}()) foreach ({{Identifier.IConfigurationSection}} _ in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
{ {
@ -512,7 +496,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
private void EmitGetBinderOptionsHelper() private void EmitGetBinderOptionsHelper()
{ {
_writer.WriteLine($$""" _writer.WriteLine($$"""
public static {{Identifier.BinderOptions}}? {{Identifier.GetBinderOptions}}({{MinimalDisplayString.NullableActionOfBinderOptions}} {{Identifier.configureOptions}}) public static {{Identifier.BinderOptions}}? {{Identifier.GetBinderOptions}}({{TypeDisplayString.NullableActionOfBinderOptions}} {{Identifier.configureOptions}})
{ {
if ({{Identifier.configureOptions}} is null) if ({{Identifier.configureOptions}} is null)
{ {
@ -524,7 +508,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if ({{Identifier.binderOptions}}.BindNonPublicProperties) if ({{Identifier.binderOptions}}.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"{{string.Format(ExceptionMessages.CannotSpecifyBindNonPublicProperties)}}"); throw new NotSupportedException($"{{string.Format(ExceptionMessages.CannotSpecifyBindNonPublicProperties)}}");
} }
return {{Identifier.binderOptions}}; return {{Identifier.binderOptions}};
@ -534,7 +518,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
private void EmitEnumParseMethod() private void EmitEnumParseMethod()
{ {
string innerExceptionTypeDisplayString = _useFullyQualifiedNames ? "global::System.Exception" : "Exception";
string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof(T)}}"); string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof(T)}}");
_writer.WriteLine($$""" _writer.WriteLine($$"""
@ -548,9 +531,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return Enum.Parse<T>(value, ignoreCase: true); return Enum.Parse<T>(value, ignoreCase: true);
#endif #endif
} }
catch ({{innerExceptionTypeDisplayString}} {{Identifier.exception}}) catch ({{Identifier.Exception}} {{Identifier.exception}})
{ {
throw new {{GetInvalidOperationDisplayName()}}($"{{exceptionArg1}}", {{Identifier.exception}}); throw new {{Identifier.InvalidOperationException}}($"{{exceptionArg1}}", {{Identifier.exception}});
} }
} }
"""); """);
@ -558,29 +541,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) private void EmitPrimitiveParseMethod(ParsableFromStringSpec type)
{ {
string innerExceptionTypeDisplayString;
string cultureInfoTypeDisplayString;
string numberStylesTypeDisplayString;
if (_useFullyQualifiedNames)
{
innerExceptionTypeDisplayString = "global::System.Exception";
cultureInfoTypeDisplayString = "global::System.Globalization.CultureInfo";
numberStylesTypeDisplayString = "global::System.Globalization.NumberStyles";
}
else
{
innerExceptionTypeDisplayString = "Exception";
cultureInfoTypeDisplayString = "CultureInfo";
numberStylesTypeDisplayString = "NumberStyles";
}
StringParsableTypeKind typeKind = type.StringParsableTypeKind; StringParsableTypeKind typeKind = type.StringParsableTypeKind;
string typeDisplayString = type.MinimalDisplayString; string typeDisplayString = type.DisplayString;
string invariantCultureExpression = $"{cultureInfoTypeDisplayString}.InvariantCulture";
string invariantCultureExpression = $"{Identifier.CultureInfo}.InvariantCulture";
string parsedValueExpr; string parsedValueExpr;
switch (typeKind) switch (typeKind)
{ {
case StringParsableTypeKind.Enum: case StringParsableTypeKind.Enum:
@ -592,12 +558,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
break; break;
case StringParsableTypeKind.Integer: case StringParsableTypeKind.Integer:
{ {
parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {numberStylesTypeDisplayString}.Integer, {invariantCultureExpression})"; parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {Identifier.NumberStyles}.Integer, {invariantCultureExpression})";
} }
break; break;
case StringParsableTypeKind.Float: case StringParsableTypeKind.Float:
{ {
parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {numberStylesTypeDisplayString}.Float, {invariantCultureExpression})"; parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {Identifier.NumberStyles}.Float, {invariantCultureExpression})";
} }
break; break;
case StringParsableTypeKind.Parse: case StringParsableTypeKind.Parse:
@ -612,7 +578,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
break; break;
case StringParsableTypeKind.CultureInfo: case StringParsableTypeKind.CultureInfo:
{ {
parsedValueExpr = $"{cultureInfoTypeDisplayString}.GetCultureInfo({Identifier.value})"; parsedValueExpr = $"{Identifier.CultureInfo}.GetCultureInfo({Identifier.value})";
} }
break; break;
case StringParsableTypeKind.Uri: case StringParsableTypeKind.Uri:
@ -635,9 +601,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
return {{parsedValueExpr}}; return {{parsedValueExpr}};
} }
catch ({{innerExceptionTypeDisplayString}} {{Identifier.exception}}) catch ({{Identifier.Exception}} {{Identifier.exception}})
{ {
throw new {{GetInvalidOperationDisplayName()}}($"{{exceptionArg1}}", {{Identifier.exception}}); throw new {{Identifier.InvalidOperationException}}($"{{exceptionArg1}}", {{Identifier.exception}});
} }
"""); """);
} }
@ -722,13 +688,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (keyType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) if (keyType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue)
{ {
// Save value to local to avoid parsing twice - during look-up and during add. // Save value to local to avoid parsing twice - during look-up and during add.
_writer.WriteLine($"{keyType.MinimalDisplayString} {Identifier.key} = {parsedKeyExpr};"); _writer.WriteLine($"{keyType.DisplayString} {Identifier.key} = {parsedKeyExpr};");
parsedKeyExpr = Identifier.key; parsedKeyExpr = Identifier.key;
} }
bool isValueType = elementType.IsValueType; bool isValueType = elementType.IsValueType;
string expressionForElementIsNotNull = $"{Identifier.element} is not null"; string expressionForElementIsNotNull = $"{Identifier.element} is not null";
string elementTypeDisplayString = elementType.MinimalDisplayString + (elementType.IsValueType ? string.Empty : "?"); string elementTypeDisplayString = elementType.DisplayString + (elementType.IsValueType ? string.Empty : "?");
string expressionForElementExists = $"{objIdentifier}.{Identifier.TryGetValue}({parsedKeyExpr}, out {elementTypeDisplayString} {Identifier.element})"; string expressionForElementExists = $"{objIdentifier}.{Identifier.TryGetValue}({parsedKeyExpr}, out {elementTypeDisplayString} {Identifier.element})";
string conditionToUseExistingElement = expressionForElementExists; string conditionToUseExistingElement = expressionForElementExists;
@ -749,7 +715,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
// we need to copy its contents into a new instance & then append/bind to that. // we need to copy its contents into a new instance & then append/bind to that.
string initExpression = collectionSpec.InitializationStrategy is InitializationStrategy.ParameterizedConstructor string initExpression = collectionSpec.InitializationStrategy is InitializationStrategy.ParameterizedConstructor
? $"new {collectionSpec.ConcreteType.MinimalDisplayString}({Identifier.element})" ? $"new {collectionSpec.ConcreteType.DisplayString}({Identifier.element})"
: $"{Identifier.element}.{collectionSpec.ToEnumerableMethodCall!}"; : $"{Identifier.element}.{collectionSpec.ToEnumerableMethodCall!}";
_writer.WriteLine($$""" _writer.WriteLine($$"""
@ -773,7 +739,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
Debug.Assert(type.NeedsMemberBinding); Debug.Assert(type.NeedsMemberBinding);
string keyCacheFieldName = GetConfigKeyCacheFieldName(type); string keyCacheFieldName = GetConfigKeyCacheFieldName(type);
string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.MinimalDisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});"; string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.DisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});";
_writer.WriteLine(validateMethodCallExpr); _writer.WriteLine(validateMethodCallExpr);
foreach (PropertySpec property in type.Properties.Values) foreach (PropertySpec property in type.Properties.Values)
@ -781,7 +747,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
bool noSetter_And_IsReadonly = !property.CanSet && property.Type is CollectionSpec { InitializationStrategy: InitializationStrategy.ParameterizedConstructor }; bool noSetter_And_IsReadonly = !property.CanSet && property.Type is CollectionSpec { InitializationStrategy: InitializationStrategy.ParameterizedConstructor };
if (property.ShouldBind() && !noSetter_And_IsReadonly) if (property.ShouldBind() && !noSetter_And_IsReadonly)
{ {
string containingTypeRef = property.IsStatic ? type.MinimalDisplayString : Identifier.obj; string containingTypeRef = property.IsStatic ? type.DisplayString : Identifier.obj;
EmitBindImplForMember( EmitBindImplForMember(
property, property,
memberAccessExpr: $"{containingTypeRef}.{property.Name}", memberAccessExpr: $"{containingTypeRef}.{property.Name}",
@ -832,7 +798,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return true; return true;
} }
string sectionValidationCall = $"{Identifier.AsConfigWithChildren}({sectionParseExpr})"; string sectionValidationCall = $"{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}({sectionParseExpr})";
string sectionIdentifier = GetIncrementalIdentifier(Identifier.section); string sectionIdentifier = GetIncrementalIdentifier(Identifier.section);
EmitStartBlock($"if ({sectionValidationCall} is {Identifier.IConfigurationSection} {sectionIdentifier})"); EmitStartBlock($"if ({sectionValidationCall} is {Identifier.IConfigurationSection} {sectionIdentifier})");
@ -869,14 +835,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
Debug.Assert(canSet); Debug.Assert(canSet);
string effectiveMemberTypeDisplayString = effectiveMemberType.MinimalDisplayString; string effectiveMemberTypeDisplayString = effectiveMemberType.DisplayString;
initKind = InitializationKind.None; initKind = InitializationKind.None;
if (memberType.SpecKind is TypeSpecKind.Nullable) if (memberType.SpecKind is TypeSpecKind.Nullable)
{ {
string nullableTempIdentifier = GetIncrementalIdentifier(Identifier.temp); string nullableTempIdentifier = GetIncrementalIdentifier(Identifier.temp);
_writer.WriteLine($"{memberType.MinimalDisplayString} {nullableTempIdentifier} = {memberAccessExpr};"); _writer.WriteLine($"{memberType.DisplayString} {nullableTempIdentifier} = {memberAccessExpr};");
_writer.WriteLine( _writer.WriteLine(
$"{effectiveMemberTypeDisplayString} {tempIdentifier} = {nullableTempIdentifier}.{Identifier.HasValue} ? {nullableTempIdentifier}.{Identifier.Value} : new {effectiveMemberTypeDisplayString}();"); $"{effectiveMemberTypeDisplayString} {tempIdentifier} = {nullableTempIdentifier}.{Identifier.HasValue} ? {nullableTempIdentifier}.{Identifier.Value} : new {effectiveMemberTypeDisplayString}();");
@ -924,7 +890,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
objIdentifier = Identifier.temp; objIdentifier = Identifier.temp;
_writer.WriteLine($$""" _writer.WriteLine($$"""
if ({{Identifier.obj}} is not {{type.PopulationCastType!.MinimalDisplayString}} {{objIdentifier}}) if ({{Identifier.obj}} is not {{type.PopulationCastType!.DisplayString}} {{objIdentifier}})
{ {
return; return;
} }
@ -936,20 +902,31 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
private void Emit_Foreach_Section_In_ConfigChildren_StartBlock() => private void Emit_Foreach_Section_In_ConfigChildren_StartBlock() =>
EmitStartBlock($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())"); EmitStartBlock($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");
private void Emit_NotSupportedException_TypeNotDetectedAsInput() =>
_writer.WriteLine(@$"throw new NotSupportedException($""{string.Format(ExceptionMessages.TypeNotDetectedAsInput, "{type}")}"");");
private static string GetSectionPathFromConfigurationExpression(string configurationKeyName) private static string GetSectionPathFromConfigurationExpression(string configurationKeyName)
=> $@"{GetSectionFromConfigurationExpression(configurationKeyName)}.{Identifier.Path}"; => $@"{GetSectionFromConfigurationExpression(configurationKeyName)}.{Identifier.Path}";
private static string GetSectionFromConfigurationExpression(string configurationKeyName, bool addQuotes = true) private static string GetSectionFromConfigurationExpression(string configurationKeyName, bool addQuotes = true)
{ {
string argExpr = addQuotes ? $@"""{configurationKeyName}""" : configurationKeyName; string argExpr = addQuotes ? $@"""{configurationKeyName}""" : configurationKeyName;
return $@"{Expression.configurationGetSection}({argExpr})"; return $@"{Identifier.configuration}.{Identifier.GetSection}({argExpr})";
}
private static string GetConditionKindExpr(ref bool isFirstType)
{
if (isFirstType)
{
isFirstType = false;
return "if";
}
return "else if";
} }
private static string GetConfigKeyCacheFieldName(ObjectSpec type) => private static string GetConfigKeyCacheFieldName(ObjectSpec type) =>
$"s_configKeys_{type.DisplayStringWithoutSpecialCharacters}"; $"s_configKeys_{type.DisplayString.ToIdentifierSubstring()}";
private void Emit_NotSupportedException_TypeNotDetectedAsInput() =>
_writer.WriteLine(@$"throw new global::System.NotSupportedException($""{string.Format(ExceptionMessages.TypeNotDetectedAsInput, "{type}")}"");");
} }
} }
} }

View file

@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
@ -10,7 +12,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
private sealed partial class Emitter private sealed partial class Emitter
{ {
private static readonly AssemblyName s_assemblyName = typeof(Emitter).Assembly.GetName(); private string? _emittedExtsTargetType;
internal static readonly AssemblyName s_assemblyName = typeof(ConfigurationBindingGenerator).Assembly.GetName();
private enum InitializationKind private enum InitializationKind
{ {
@ -26,29 +30,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public const string sectionPath = "section.Path"; public const string sectionPath = "section.Path";
public const string sectionValue = "section.Value"; public const string sectionValue = "section.Value";
public const string GetBinderOptions = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{Identifier.GetBinderOptions}"; public static string GeneratedCodeAnnotation = $@"[GeneratedCode(""{s_assemblyName.Name}"", ""{s_assemblyName.Version}"")]";
} }
private static class FullyQualifiedDisplayString private static class TypeDisplayString
{
public const string ActionOfBinderOptions = $"global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>";
public const string AddSingleton = $"{ServiceCollectionServiceExtensions}.AddSingleton";
public const string ConfigurationChangeTokenSource = "global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource";
public const string CoreBindingHelper = $"global::{ProjectName}.{Identifier.CoreBindingHelper}";
public const string IConfiguration = "global::Microsoft.Extensions.Configuration.IConfiguration";
public const string IConfigurationSection = IConfiguration + "Section";
public const string IOptionsChangeTokenSource = "global::Microsoft.Extensions.Options.IOptionsChangeTokenSource";
public const string InvalidOperationException = "global::System.InvalidOperationException";
public const string IServiceCollection = "global::Microsoft.Extensions.DependencyInjection.IServiceCollection";
public const string NotSupportedException = "global::System.NotSupportedException";
public const string OptionsBuilderOfTOptions = $"global::Microsoft.Extensions.Options.OptionsBuilder<{Identifier.TOptions}>";
public const string ServiceCollectionServiceExtensions = "global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions";
public const string Type = $"global::System.Type";
}
private static class MinimalDisplayString
{ {
public const string NullableActionOfBinderOptions = "Action<BinderOptions>?"; public const string NullableActionOfBinderOptions = "Action<BinderOptions>?";
public const string OptionsBuilderOfTOptions = $"OptionsBuilder<{Identifier.TOptions}>";
public const string HashSetOfString = "HashSet<string>"; public const string HashSetOfString = "HashSet<string>";
public const string LazyHashSetOfString = "Lazy<HashSet<string>>"; public const string LazyHashSetOfString = "Lazy<HashSet<string>>";
public const string ListOfString = "List<string>"; public const string ListOfString = "List<string>";
@ -75,6 +63,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public const string services = nameof(services); public const string services = nameof(services);
public const string temp = nameof(temp); public const string temp = nameof(temp);
public const string type = nameof(type); public const string type = nameof(type);
public const string typedObj = nameof(typedObj);
public const string validateKeys = nameof(validateKeys); public const string validateKeys = nameof(validateKeys);
public const string value = nameof(value); public const string value = nameof(value);
@ -82,21 +71,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public const string AddSingleton = nameof(AddSingleton); public const string AddSingleton = nameof(AddSingleton);
public const string Any = nameof(Any); public const string Any = nameof(Any);
public const string Array = nameof(Array); public const string Array = nameof(Array);
public const string AsConfigWithChildren = nameof(AsConfigWithChildren);
public const string Bind = nameof(Bind); public const string Bind = nameof(Bind);
public const string BinderOptions = nameof(BinderOptions); public const string BinderOptions = nameof(BinderOptions);
public const string BindingExtensions = nameof(BindingExtensions);
public const string ConfigurationChangeTokenSource = nameof(ConfigurationChangeTokenSource);
public const string Configure = nameof(Configure); public const string Configure = nameof(Configure);
public const string CopyTo = nameof(CopyTo); public const string CopyTo = nameof(CopyTo);
public const string ContainsKey = nameof(ContainsKey); public const string ContainsKey = nameof(ContainsKey);
public const string CoreBindingHelper = nameof(CoreBindingHelper);
public const string Count = nameof(Count); public const string Count = nameof(Count);
public const string CultureInfo = nameof(CultureInfo); public const string CultureInfo = nameof(CultureInfo);
public const string CultureNotFoundException = nameof(CultureNotFoundException); public const string CultureNotFoundException = nameof(CultureNotFoundException);
public const string Enum = nameof(Enum); public const string Enum = nameof(Enum);
public const string ErrorOnUnknownConfiguration = nameof(ErrorOnUnknownConfiguration); public const string ErrorOnUnknownConfiguration = nameof(ErrorOnUnknownConfiguration);
public const string GeneratedConfigurationBinder = nameof(GeneratedConfigurationBinder); public const string Exception = nameof(Exception);
public const string GeneratedOptionsBuilderBinder = nameof(GeneratedOptionsBuilderBinder);
public const string GeneratedServiceCollectionBinder = nameof(GeneratedServiceCollectionBinder);
public const string Get = nameof(Get); public const string Get = nameof(Get);
public const string GetBinderOptions = nameof(GetBinderOptions); public const string GetBinderOptions = nameof(GetBinderOptions);
public const string GetChildren = nameof(GetChildren); public const string GetChildren = nameof(GetChildren);
@ -108,9 +95,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public const string IConfiguration = nameof(IConfiguration); public const string IConfiguration = nameof(IConfiguration);
public const string IConfigurationSection = nameof(IConfigurationSection); public const string IConfigurationSection = nameof(IConfigurationSection);
public const string Int32 = "int"; public const string Int32 = "int";
public const string InterceptsLocation = nameof(InterceptsLocation);
public const string InvalidOperationException = nameof(InvalidOperationException); public const string InvalidOperationException = nameof(InvalidOperationException);
public const string InvariantCulture = nameof(InvariantCulture); public const string InvariantCulture = nameof(InvariantCulture);
public const string IOptionsChangeTokenSource = nameof(IOptionsChangeTokenSource);
public const string IServiceCollection = nameof(IServiceCollection);
public const string Length = nameof(Length); public const string Length = nameof(Length);
public const string NumberStyles = nameof(NumberStyles);
public const string Parse = nameof(Parse); public const string Parse = nameof(Parse);
public const string Path = nameof(Path); public const string Path = nameof(Path);
public const string Resize = nameof(Resize); public const string Resize = nameof(Resize);
@ -118,17 +109,66 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public const string TOptions = nameof(TOptions); public const string TOptions = nameof(TOptions);
public const string TryCreate = nameof(TryCreate); public const string TryCreate = nameof(TryCreate);
public const string TryGetValue = nameof(TryGetValue); public const string TryGetValue = nameof(TryGetValue);
public const string TryParse = nameof(TryParse); public const string Type = nameof(Type);
public const string Uri = nameof(Uri); public const string Uri = nameof(Uri);
public const string ValidateConfigurationKeys = nameof(ValidateConfigurationKeys); public const string ValidateConfigurationKeys = nameof(ValidateConfigurationKeys);
public const string Value = nameof(Value); public const string Value = nameof(Value);
} }
private bool ShouldEmitBinders() => private bool ShouldEmitBindingExtensions() =>
ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Any) || ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Any) ||
ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Any) || ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Any) ||
ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Any); ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Any);
private void EmitInterceptsLocationAnnotations(Enum generatedBindingOverload)
{
// The only time a generated binding method won't have any locations to
// intercept is when either of these methods are used as helpers for
// other generated OptionsBuilder or ServiceCollection binding extensions.
bool interceptsCalls = _sourceGenSpec.InterceptionInfo.TryGetValue(generatedBindingOverload, out List<InterceptorLocationInfo>? infoList);
Debug.Assert(interceptsCalls ||
generatedBindingOverload is MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions ||
generatedBindingOverload is MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions);
if (interceptsCalls)
{
EmitInterceptsLocationAnnotations(infoList);
}
}
private void EmitInterceptsLocationAnnotations(List<InterceptorLocationInfo> infoList)
{
foreach (InterceptorLocationInfo info in infoList)
{
_writer.WriteLine($@"[{Identifier.InterceptsLocation}Attribute(@""{info.FilePath}"", {info.LineNumber}, {info.CharacterNumber})]");
}
}
private void EmitBindingExtStartRegion(string targetType)
{
Debug.Assert(_emittedExtsTargetType is null);
EmitBlankLineIfRequired();
_emittedExtsTargetType = targetType;
EmitBindingExtRegionText(isStart: true);
_emitBlankLineBeforeNextStatement = false;
}
private void EmitBindingExtEndRegion()
{
Debug.Assert(_emittedExtsTargetType is not null);
EmitBindingExtRegionText(isStart: false);
_emittedExtsTargetType = null;
_emitBlankLineBeforeNextStatement = true;
}
private void EmitBindingExtRegionText(bool isStart)
{
string endSource = isStart ? string.Empty : "end";
_writer.WriteLine($"#{endSource}region {_emittedExtsTargetType} extensions.");
}
/// <summary> /// <summary>
/// Starts a block of source code. /// Starts a block of source code.
/// </summary> /// </summary>
@ -171,24 +211,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
_emitBlankLineBeforeNextStatement = true; _emitBlankLineBeforeNextStatement = true;
} }
private void EmitCheckForNullArgument_WithBlankLine_IfRequired(bool isValueType)
{
if (!isValueType)
{
EmitCheckForNullArgument_WithBlankLine(Identifier.obj);
}
}
private void EmitCheckForNullArgument_WithBlankLine(string paramName) private void EmitCheckForNullArgument_WithBlankLine(string paramName)
{ {
string exceptionTypeDisplayString = _useFullyQualifiedNames
? "global::System.ArgumentNullException"
: "ArgumentNullException";
_writer.WriteLine($$""" _writer.WriteLine($$"""
if ({{paramName}} is null) if ({{paramName}} is null)
{ {
throw new {{exceptionTypeDisplayString}}(nameof({{paramName}})); throw new ArgumentNullException(nameof({{paramName}}));
} }
"""); """);
@ -201,51 +229,28 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (!type.CanInitialize) if (!type.CanInitialize)
{ {
_writer.WriteLine(GetInitException(type.InitExceptionMessage) + ";"); _writer.WriteLine($@"throw new {Identifier.InvalidOperationException}(""{type.InitExceptionMessage}"");");
return true; return true;
} }
return false; return false;
} }
private void EmitRootBindingClassStartBlock(string className)
{
EmitBlankLineIfRequired();
EmitStartBlock($$"""
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
{{GetGeneratedCodeAttributeSrc()}}
internal static class {{className}}
""");
_emitBlankLineBeforeNextStatement = false;
}
private string GetGeneratedCodeAttributeSrc()
{
string attributeRefExpr = _useFullyQualifiedNames ? $"global::System.CodeDom.Compiler.GeneratedCodeAttribute" : "GeneratedCode";
return $@"[{attributeRefExpr}(""{s_assemblyName.Name}"", ""{s_assemblyName.Version}"")]";
}
private string GetInitException(string message) => $@"throw new {GetInvalidOperationDisplayName()}(""{message}"")";
private string GetIncrementalIdentifier(string prefix) => $"{prefix}{_valueSuffixIndex++}"; private string GetIncrementalIdentifier(string prefix) => $"{prefix}{_valueSuffixIndex++}";
private string GetInitalizeMethodDisplayString(ObjectSpec type) => private static string GetInitalizeMethodDisplayString(ObjectSpec type) =>
GetHelperMethodDisplayString($"{nameof(MethodsToGen_CoreBindingHelper.Initialize)}{type.DisplayStringWithoutSpecialCharacters}"); $"{nameof(MethodsToGen_CoreBindingHelper.Initialize)}{type.DisplayString.ToIdentifierSubstring()}";
private string GetTypeDisplayString(TypeSpec type) => _useFullyQualifiedNames ? type.FullyQualifiedDisplayString : type.MinimalDisplayString;
private string GetHelperMethodDisplayString(string methodName)
{
if (_useFullyQualifiedNames)
{
methodName = FullyQualifiedDisplayString.CoreBindingHelper + "." + methodName;
}
return methodName;
}
private string GetInvalidOperationDisplayName() => _useFullyQualifiedNames ? FullyQualifiedDisplayString.InvalidOperationException : Identifier.InvalidOperationException;
} }
} }
internal static class EmitterExtensions
{
public static string ToIdentifierSubstring(this string typeDisplayName) =>
typeDisplayName
.Replace("[]", nameof(Array))
.Replace(", ", string.Empty)
.Replace(".", string.Empty)
.Replace("<", string.Empty)
.Replace(">", string.Empty);
}
} }

View file

@ -9,19 +9,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
private bool ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder methods) => (_sourceGenSpec.MethodsToGen_OptionsBuilderExt & methods) != 0; private bool ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder methods) => (_sourceGenSpec.MethodsToGen_OptionsBuilderExt & methods) != 0;
private void EmitBinder_Extensions_OptionsBuilder() private void EmitBindingExtensions_OptionsBuilder()
{ {
if (!ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Any)) if (!ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Any))
{ {
return; return;
} }
EmitRootBindingClassStartBlock(Identifier.GeneratedOptionsBuilderBinder); EmitBindingExtStartRegion(TypeDisplayString.OptionsBuilderOfTOptions);
EmitBindMethods_Extensions_OptionsBuilder(); EmitBindMethods_Extensions_OptionsBuilder();
EmitBindConfigurationMethod(); EmitBindConfigurationMethod();
EmitBindingExtEndRegion();
EmitEndBlock();
} }
private void EmitBindMethods_Extensions_OptionsBuilder() private void EmitBindMethods_Extensions_OptionsBuilder()
@ -32,24 +30,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
const string documentation = @"/// <summary>Registers a configuration instance which <typeparamref name=""TOptions""/> will bind against.</summary>"; const string documentation = @"/// <summary>Registers a configuration instance which <typeparamref name=""TOptions""/> will bind against.</summary>";
const string paramList = $"{FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}"; const string paramList = $"{Identifier.IConfiguration} {Identifier.configuration}";
if (ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Bind_T)) if (ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Bind_T))
{ {
EmitMethodStartBlock("Bind", paramList, documentation); EmitMethodStartBlock(MethodsToGen_Extensions_OptionsBuilder.Bind_T, "Bind", paramList, documentation);
_writer.WriteLine($"return global::{Identifier.GeneratedOptionsBuilderBinder}.Bind({Identifier.optionsBuilder}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); _writer.WriteLine($"return Bind({Identifier.optionsBuilder}, {Identifier.configuration}, {Identifier.configureOptions}: null);");
EmitEndBlock(); EmitEndBlock();
} }
EmitMethodStartBlock( EmitMethodStartBlock(
MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions,
"Bind", "Bind",
paramList + $", {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}", paramList + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}",
documentation); documentation);
EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder); EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder);
_writer.WriteLine($$""" _writer.WriteLine($$"""
global::{{Identifier.GeneratedServiceCollectionBinder}}.{{Identifier.Configure}}<{{Identifier.TOptions}}>({{Identifier.optionsBuilder}}.{{Identifier.Services}}, {{Identifier.optionsBuilder}}.Name, {{Identifier.configuration}}, {{Identifier.configureOptions}}); {{Identifier.Configure}}<{{Identifier.TOptions}}>({{Identifier.optionsBuilder}}.{{Identifier.Services}}, {{Identifier.optionsBuilder}}.Name, {{Identifier.configuration}}, {{Identifier.configureOptions}});
return {{Identifier.optionsBuilder}}; return {{Identifier.optionsBuilder}};
"""); """);
@ -63,19 +62,20 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
const string documentation = $@"/// <summary>Registers the dependency injection container to bind <typeparamref name=""TOptions""/> against the <see cref=""{FullyQualifiedDisplayString.IConfiguration}""/> obtained from the DI service provider.</summary>"; const string documentation = $@"/// <summary>Registers the dependency injection container to bind <typeparamref name=""TOptions""/> against the <see cref=""{Identifier.IConfiguration}""/> obtained from the DI service provider.</summary>";
string paramList = $"string {Identifier.configSectionPath}, {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions} = null"; string paramList = $"string {Identifier.configSectionPath}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions} = null";
EmitMethodStartBlock("BindConfiguration", paramList, documentation); EmitMethodStartBlock(MethodsToGen_Extensions_OptionsBuilder.BindConfiguration, "BindConfiguration", paramList, documentation);
EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder); EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder);
EmitCheckForNullArgument_WithBlankLine(Identifier.configSectionPath); EmitCheckForNullArgument_WithBlankLine(Identifier.configSectionPath);
EmitStartBlock($"{Identifier.optionsBuilder}.{Identifier.Configure}<{FullyQualifiedDisplayString.IConfiguration}>(({Identifier.obj}, {Identifier.configuration}) =>"); EmitStartBlock($"{Identifier.optionsBuilder}.{Identifier.Configure}<{Identifier.IConfiguration}>(({Identifier.obj}, {Identifier.configuration}) =>");
EmitCheckForNullArgument_WithBlankLine(Identifier.obj);
EmitCheckForNullArgument_WithBlankLine(Identifier.configuration);
_writer.WriteLine($$""" _writer.WriteLine($$"""
{{FullyQualifiedDisplayString.IConfiguration}} {{Identifier.section}} = string.Equals(string.Empty, {{Identifier.configSectionPath}}, global::System.StringComparison.OrdinalIgnoreCase) ? {{Identifier.configuration}} : {{Identifier.configuration}}.{{Identifier.GetSection}}({{Identifier.configSectionPath}}); {{Identifier.IConfiguration}} {{Identifier.section}} = string.Equals(string.Empty, {{Identifier.configSectionPath}}, StringComparison.OrdinalIgnoreCase) ? {{Identifier.configuration}} : {{Identifier.configuration}}.{{Identifier.GetSection}}({{Identifier.configSectionPath}});
{{FullyQualifiedDisplayString.CoreBindingHelper}}.{{nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)}}({{Identifier.section}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}}); {{nameof(MethodsToGen_CoreBindingHelper.BindCoreMain)}}({{Identifier.section}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}});
"""); """);
EmitEndBlock(endBraceTrailingSource: ");"); EmitEndBlock(endBraceTrailingSource: ");");
@ -83,20 +83,20 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
_writer.WriteLine(); _writer.WriteLine();
_writer.WriteLine($$""" _writer.WriteLine($$"""
{{FullyQualifiedDisplayString.AddSingleton}}<{{FullyQualifiedDisplayString.IOptionsChangeTokenSource}}<{{Identifier.TOptions}}>, {{FullyQualifiedDisplayString.ConfigurationChangeTokenSource}}<{{Identifier.TOptions}}>>({{Identifier.optionsBuilder}}.{{Identifier.Services}}); {{Identifier.optionsBuilder}}.{{Identifier.Services}}.{{Identifier.AddSingleton}}<{{Identifier.IOptionsChangeTokenSource}}<{{Identifier.TOptions}}>, {{Identifier.ConfigurationChangeTokenSource}}<{{Identifier.TOptions}}>>();
return {{Identifier.optionsBuilder}}; return {{Identifier.optionsBuilder}};
"""); """);
EmitEndBlock(); EmitEndBlock();
} }
private void EmitMethodStartBlock(string methodName, string paramList, string documentation) private void EmitMethodStartBlock(MethodsToGen_Extensions_OptionsBuilder method, string methodName, string paramList, string documentation)
{ {
paramList = $"this {FullyQualifiedDisplayString.OptionsBuilderOfTOptions} {Identifier.optionsBuilder}, {paramList}"; paramList = $"this {TypeDisplayString.OptionsBuilderOfTOptions} {Identifier.optionsBuilder}, {paramList}";
EmitBlankLineIfRequired(); EmitBlankLineIfRequired();
_writer.WriteLine(documentation); _writer.WriteLine(documentation);
EmitStartBlock($"public static {FullyQualifiedDisplayString.OptionsBuilderOfTOptions} {methodName}<{Identifier.TOptions}>({paramList}) where {Identifier.TOptions} : class"); EmitInterceptsLocationAnnotations(method);
EmitStartBlock($"public static {TypeDisplayString.OptionsBuilderOfTOptions} {methodName}<{Identifier.TOptions}>({paramList}) where {Identifier.TOptions} : class");
} }
} }
} }

View file

@ -9,71 +9,72 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
private bool ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection methods) => (_sourceGenSpec.MethodsToGen_ServiceCollectionExt & methods) != 0; private bool ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection methods) => (_sourceGenSpec.MethodsToGen_ServiceCollectionExt & methods) != 0;
private void EmitBinder_Extensions_IServiceCollection() private void EmitBindingExtensions_IServiceCollection()
{ {
if (!ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Any)) if (!ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Any))
{ {
return; return;
} }
EmitRootBindingClassStartBlock(Identifier.GeneratedServiceCollectionBinder); EmitBindingExtStartRegion(Identifier.IServiceCollection);
EmitConfigureMethods();
EmitBindingExtEndRegion();
}
private void EmitConfigureMethods()
{
const string defaultNameExpr = "string.Empty"; const string defaultNameExpr = "string.Empty";
const string configureMethodString = $"global::{Identifier.GeneratedServiceCollectionBinder}.{Identifier.Configure}"; string configParam = $"{Identifier.IConfiguration} {Identifier.configuration}";
string configParam = $"{FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}";
if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T)) if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T))
{ {
EmitStartMethod(configParam); EmitStartMethod(MethodsToGen_Extensions_ServiceCollection.Configure_T, configParam);
_writer.WriteLine($"return {configureMethodString}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); _writer.WriteLine($"return {Identifier.Configure}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.configuration}, {Identifier.configureOptions}: null);");
EmitEndBlock(); EmitEndBlock();
} }
if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T_name)) if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T_name))
{ {
EmitStartMethod( EmitStartMethod(
MethodsToGen_Extensions_ServiceCollection.Configure_T_name,
paramList: $"string? {Identifier.name}, " + configParam); paramList: $"string? {Identifier.name}, " + configParam);
_writer.WriteLine($"return {configureMethodString}<{Identifier.TOptions}>({Identifier.services}, {Identifier.name}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); _writer.WriteLine($"return {Identifier.Configure}<{Identifier.TOptions}>({Identifier.services}, {Identifier.name}, {Identifier.configuration}, {Identifier.configureOptions}: null);");
EmitEndBlock(); EmitEndBlock();
} }
if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T_BinderOptions)) if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T_BinderOptions))
{ {
EmitStartMethod( EmitStartMethod(
paramList: configParam + $", {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}"); MethodsToGen_Extensions_ServiceCollection.Configure_T_BinderOptions,
_writer.WriteLine($"return {configureMethodString}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.configuration}, {Identifier.configureOptions});"); paramList: configParam + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}");
_writer.WriteLine($"return {Identifier.Configure}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.configuration}, {Identifier.configureOptions});");
EmitEndBlock(); EmitEndBlock();
} }
// Core Configure method that the other overloads call. // Core Configure method that the other overloads call.
// Like the others, it is public API that could be called directly by users. // Like the others, it is public API that could be called directly by users.
// So, it is always generated whenever a Configure overload is called. // So, it is always generated whenever a Configure overload is called.
string optionsNamespaceName = "global::Microsoft.Extensions.Options"; string optionsNamespaceName = "Microsoft.Extensions.Options";
string bindCoreUntypedDisplayString = GetHelperMethodDisplayString(nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped));
EmitStartMethod(paramList: $"string? {Identifier.name}, " + configParam + $", {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}"); EmitStartMethod(MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions, paramList: $"string? {Identifier.name}, " + configParam + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}");
EmitCheckForNullArgument_WithBlankLine(Identifier.services); EmitCheckForNullArgument_WithBlankLine(Identifier.services);
EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); EmitCheckForNullArgument_WithBlankLine(Identifier.configuration);
_writer.WriteLine($$""" _writer.WriteLine($$"""
global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions({{Identifier.services}}); OptionsServiceCollectionExtensions.AddOptions({{Identifier.services}});
{{FullyQualifiedDisplayString.AddSingleton}}<{{FullyQualifiedDisplayString.IOptionsChangeTokenSource}}<{{Identifier.TOptions}}>>({{Identifier.services}}, new {{FullyQualifiedDisplayString.ConfigurationChangeTokenSource}}<{{Identifier.TOptions}}>({{Identifier.name}}, {{Identifier.configuration}})); {{Identifier.services}}.{{Identifier.AddSingleton}}<{{Identifier.IOptionsChangeTokenSource}}<{{Identifier.TOptions}}>>(new {{Identifier.ConfigurationChangeTokenSource}}<{{Identifier.TOptions}}>({{Identifier.name}}, {{Identifier.configuration}}));
return {{FullyQualifiedDisplayString.AddSingleton}}<{{optionsNamespaceName}}.IConfigureOptions<{{Identifier.TOptions}}>>({{Identifier.services}}, new {{optionsNamespaceName}}.ConfigureNamedOptions<{{Identifier.TOptions}}>({{Identifier.name}}, {{Identifier.obj}} => {{bindCoreUntypedDisplayString}}({{Identifier.configuration}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}}))); return {{Identifier.services}}.{{Identifier.AddSingleton}}<{{optionsNamespaceName}}.IConfigureOptions<{{Identifier.TOptions}}>>(new {{optionsNamespaceName}}.ConfigureNamedOptions<{{Identifier.TOptions}}>({{Identifier.name}}, {{Identifier.obj}} => {{nameof(MethodsToGen_CoreBindingHelper.BindCoreMain)}}({{Identifier.configuration}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}){{Identifier.configureOptions}})));
"""); """);
EmitEndBlock(); EmitEndBlock();
EmitEndBlock();
_emitBlankLineBeforeNextStatement = true;
} }
private void EmitStartMethod(string paramList) private void EmitStartMethod(MethodsToGen_Extensions_ServiceCollection overload, string paramList)
{ {
paramList = $"this {FullyQualifiedDisplayString.IServiceCollection} {Identifier.services}, {paramList}"; paramList = $"this {Identifier.IServiceCollection} {Identifier.services}, {paramList}";
EmitBlankLineIfRequired(); EmitBlankLineIfRequired();
EmitStartBlock($$""" _writer.WriteLine("/// <summary>Registers a configuration instance which TOptions will bind against.</summary>");
/// <summary>Registers a configuration instance which TOptions will bind against.</summary> EmitInterceptsLocationAnnotations(overload);
public static {{FullyQualifiedDisplayString.IServiceCollection}} {{Identifier.Configure}}<{{Identifier.TOptions}}>({{paramList}}) where {{Identifier.TOptions}} : class EmitStartBlock($"public static {Identifier.IServiceCollection} {Identifier.Configure}<{Identifier.TOptions}>({paramList}) where {Identifier.TOptions} : class");
""");
} }
} }
} }

View file

@ -0,0 +1,90 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
internal sealed record InterceptorLocationInfo
{
public InterceptorLocationInfo(IInvocationOperation operation)
{
SyntaxNode operationSyntax = operation.Syntax;
TextSpan operationSpan = operationSyntax.Span;
SyntaxTree operationSyntaxTree = operationSyntax.SyntaxTree;
FilePath = GetInterceptorFilePath(operationSyntaxTree, operation.SemanticModel?.Compilation.Options.SourceReferenceResolver);
FileLinePositionSpan span = operationSyntaxTree.GetLineSpan(operationSpan);
LineNumber = span.StartLinePosition.Line + 1;
// Calculate the character offset to the end of the binding invocation detected.
int invocationLength = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)operationSyntax).Expression).Expression.Span.Length;
CharacterNumber = span.StartLinePosition.Character + invocationLength + 2;
}
public string FilePath { get; }
public int LineNumber { get; }
public int CharacterNumber { get; }
// Utilize 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
private static string GetInterceptorFilePath(SyntaxTree tree, SourceReferenceResolver? resolver) =>
resolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath;
}
internal sealed record ConfigurationBinderInterceptorInfo
{
private OverloadInterceptorInfo? _bind_Instance;
private OverloadInterceptorInfo? _bind_instance_BinderOptions;
private OverloadInterceptorInfo? _bind_key_instance;
public void RegisterOverloadInfo(MethodsToGen_ConfigurationBinder overload, TypeSpec type, IInvocationOperation operation)
{
OverloadInterceptorInfo overloadInfo = DetermineOverload(overload, initIfNull: true);
overloadInfo.RegisterLocationInfo(type, operation);
}
public OverloadInterceptorInfo GetOverloadInfo(MethodsToGen_ConfigurationBinder overload) =>
DetermineOverload(overload, initIfNull: false) ?? throw new ArgumentNullException(nameof(overload));
private OverloadInterceptorInfo? DetermineOverload(MethodsToGen_ConfigurationBinder overload, bool initIfNull)
{
return overload switch
{
MethodsToGen_ConfigurationBinder.Bind_instance => InitIfNull(ref _bind_Instance),
MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions => InitIfNull(ref _bind_instance_BinderOptions),
MethodsToGen_ConfigurationBinder.Bind_key_instance => InitIfNull(ref _bind_key_instance),
_ => throw new InvalidOperationException(nameof(overload)),
};
OverloadInterceptorInfo InitIfNull(ref OverloadInterceptorInfo? info)
{
if (initIfNull)
{
info ??= new OverloadInterceptorInfo();
}
return info;
}
}
}
internal sealed record OverloadInterceptorInfo : IEnumerable<KeyValuePair<TypeSpec, List<InterceptorLocationInfo>>>
{
private readonly Dictionary<TypeSpec, List<InterceptorLocationInfo>> _typeInterceptionInfo = new();
public void RegisterLocationInfo(TypeSpec type, IInvocationOperation operation) =>
_typeInterceptionInfo.RegisterCacheEntry(type, new InterceptorLocationInfo(operation));
public IEnumerator<KeyValuePair<TypeSpec, List<InterceptorLocationInfo>>> GetEnumerator() => _typeInterceptionInfo.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View file

@ -10,11 +10,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
None = 0x0, None = 0x0,
BindCore = 0x1, BindCore = 0x1,
BindCoreUntyped = 0x2, GetCore = 0x2,
GetCore = 0x4, GetValueCore = 0x4,
GetValueCore = 0x8, BindCoreMain = 0x8,
Initialize = 0x10, Initialize = 0x10,
AsConfigWithChildren = 0x20, HasValueOrChildren = 0x20,
AsConfigWithChildren = 0x40,
} }
/// <summary> /// <summary>

View file

@ -40,7 +40,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
IsValidMethodName_OptionsConfigurationServiceCollectionExtensions(name); IsValidMethodName_OptionsConfigurationServiceCollectionExtensions(name);
} }
private static bool IsBindingOperation(IInvocationOperation operation) public static bool IsBindingOperation(IInvocationOperation operation)
{ {
if (operation.TargetMethod is not IMethodSymbol if (operation.TargetMethod is not IMethodSymbol
{ {

View file

@ -102,15 +102,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec) if (GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec)
{ {
Dictionary<MethodsToGen_ConfigurationBinder, HashSet<TypeSpec>> types = _sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods; RegisterAsInterceptor_ConfigBinder_BindMethod(overload, typeSpec, invocation.Operation);
if (!types.TryGetValue(overload, out HashSet<TypeSpec>? typeSpecs))
{
types[overload] = typeSpecs = new HashSet<TypeSpec>();
}
_sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload;
typeSpecs.Add(typeSpec);
} }
static ITypeSymbol? ResolveType(IOperation conversionOperation) => static ITypeSymbol? ResolveType(IOperation conversionOperation) =>
@ -184,8 +176,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (GetTargetTypeForRootInvocation(type, invocation.Location) is TypeSpec typeSpec) if (GetTargetTypeForRootInvocation(type, invocation.Location) is TypeSpec typeSpec)
{ {
_sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; RegisterAsInterceptor_ConfigBinder(overload, invocation.Operation);
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, typeSpec); RegisterTypeForGetCoreGen(typeSpec);
} }
} }
@ -253,10 +245,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (IsParsableFromString(effectiveType, out _) && if (IsParsableFromString(effectiveType, out _) &&
GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec) GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec)
{ {
_sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; RegisterAsInterceptor_ConfigBinder(overload, invocation.Operation);
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, typeSpec); RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, typeSpec);
} }
} }
private void RegisterAsInterceptor_ConfigBinder(MethodsToGen_ConfigurationBinder overload, IInvocationOperation operation)
{
_sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload;
RegisterAsInterceptor(overload, operation);
}
/// <summary>
/// Registers generated Bind methods as interceptors. This is done differently from other root
/// methods <see cref="RegisterAsInterceptor(Enum, IInvocationOperation)"/> because we need to
/// explicitly account for the type to bind, to avoid type-check issues for polymorphic objects.
/// </summary>
private void RegisterAsInterceptor_ConfigBinder_BindMethod(MethodsToGen_ConfigurationBinder overload, TypeSpec typeSpec, IInvocationOperation operation)
{
_sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload;
_sourceGenSpec.InterceptionInfo_ConfigBinder.RegisterOverloadInfo(overload, typeSpec, operation);
}
} }
} }
} }

View file

@ -34,9 +34,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
// We are going to emit calls to APIs on IServiceCollection.
_sourceGenSpec.TypeNamespaces.Add("Microsoft.Extensions.DependencyInjection");
if (targetMethod.Name is "Bind") if (targetMethod.Name is "Bind")
{ {
RegisterBindInvocation(invocation, typeSpec); RegisterBindInvocation(invocation, typeSpec);
@ -61,20 +58,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
if (paramCount is 2) MethodsToGen_Extensions_OptionsBuilder overload = paramCount switch
{ {
_sourceGenSpec.MethodsToGen_OptionsBuilderExt |= MethodsToGen_Extensions_OptionsBuilder.Bind_T; 2 => MethodsToGen_Extensions_OptionsBuilder.Bind_T,
} 3 when SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type) =>
else if (paramCount is 3 && SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type)) MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions,
{ _ => MethodsToGen_Extensions_OptionsBuilder.None
_sourceGenSpec.MethodsToGen_OptionsBuilderExt |= MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions; };
}
else
{
return;
}
RegisterTypeForMethodGen(MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions, typeSpec); if (overload is not MethodsToGen_Extensions_OptionsBuilder.None)
{
RegisterAsInterceptor_OptionsBuilder(overload, operation);
RegisterTypeForMethodGen(MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions, typeSpec);
}
} }
private void ParseBindConfigurationInvocation(BinderInvocation invocation, TypeSpec typeSpec) private void ParseBindConfigurationInvocation(BinderInvocation invocation, TypeSpec typeSpec)
@ -85,12 +81,26 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
int paramCount = @params.Length; int paramCount = @params.Length;
Debug.Assert(paramCount >= 2); Debug.Assert(paramCount >= 2);
if (paramCount is 3 && @params[1].Type.SpecialType is SpecialType.System_String && SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type)) if (paramCount is 3 &&
@params[1].Type.SpecialType is SpecialType.System_String &&
SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type))
{ {
_sourceGenSpec.MethodsToGen_OptionsBuilderExt |= MethodsToGen_Extensions_OptionsBuilder.BindConfiguration_T_path_BinderOptions; RegisterAsInterceptor_OptionsBuilder(MethodsToGen_Extensions_OptionsBuilder.BindConfiguration_T_path_BinderOptions, invocation.Operation);
RegisterTypeForBindCoreUntypedGen(typeSpec); RegisterTypeForBindCoreMainGen(typeSpec);
} }
} }
private void RegisterAsInterceptor_OptionsBuilder(MethodsToGen_Extensions_OptionsBuilder overload, IInvocationOperation operation)
{
_sourceGenSpec.MethodsToGen_OptionsBuilderExt |= overload;
RegisterAsInterceptor(overload, operation);
// Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource.
_sourceGenSpec.Namespaces.Add("Microsoft.Extensions.Options");
// Emitting refs to OptionsBuilder<T>.
_sourceGenSpec.Namespaces.Add("Microsoft.Extensions.DependencyInjection");
}
} }
} }
} }

View file

@ -79,12 +79,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
RegisterTypeForMethodGen(overload, typeSpec); RegisterTypeForMethodGen(overload, typeSpec);
RegisterAsInterceptor(overload, operation);
} }
private void RegisterTypeForMethodGen(MethodsToGen_Extensions_ServiceCollection overload, TypeSpec typeSpec) private void RegisterTypeForMethodGen(MethodsToGen_Extensions_ServiceCollection overload, TypeSpec typeSpec)
{ {
_sourceGenSpec.MethodsToGen_ServiceCollectionExt |= overload; _sourceGenSpec.MethodsToGen_ServiceCollectionExt |= overload;
RegisterTypeForBindCoreUntypedGen(typeSpec); _sourceGenSpec.Namespaces.Add("Microsoft.Extensions.DependencyInjection");
RegisterTypeForBindCoreMainGen(typeSpec);
} }
} }
} }

View file

@ -10,7 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.7.0-3.23314.3" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -26,12 +26,14 @@
<Compile Include="ConfigurationBindingGenerator.cs" /> <Compile Include="ConfigurationBindingGenerator.cs" />
<Compile Include="ConfigurationBindingGenerator.Emitter.cs" /> <Compile Include="ConfigurationBindingGenerator.Emitter.cs" />
<Compile Include="ConfigurationBindingGenerator.Parser.cs" /> <Compile Include="ConfigurationBindingGenerator.Parser.cs" />
<Compile Include="ConfigurationBindingGenerator.Suppressor.cs" />
<Compile Include="Helpers\Emitter\ConfigurationBinder.cs" /> <Compile Include="Helpers\Emitter\ConfigurationBinder.cs" />
<Compile Include="Helpers\Emitter\CoreBindingHelper.cs" /> <Compile Include="Helpers\Emitter\CoreBindingHelpers.cs" />
<Compile Include="Helpers\Emitter\ExceptionMessages.cs" /> <Compile Include="Helpers\Emitter\ExceptionMessages.cs" />
<Compile Include="Helpers\Emitter\Helpers.cs" /> <Compile Include="Helpers\Emitter\Helpers.cs" />
<Compile Include="Helpers\Emitter\OptionsBuilderConfigurationExtensions.cs" /> <Compile Include="Helpers\Emitter\OptionsBuilderConfigurationExtensions.cs" />
<Compile Include="Helpers\Emitter\OptionsConfigurationServiceCollectionExtensions.cs" /> <Compile Include="Helpers\Emitter\OptionsConfigurationServiceCollectionExtensions.cs" />
<Compile Include="Helpers\InterceptorLocationInfo.cs" />
<Compile Include="Helpers\MethodsToGen.cs" /> <Compile Include="Helpers\MethodsToGen.cs" />
<Compile Include="Helpers\Parser\BinderInvocation.cs" /> <Compile Include="Helpers\Parser\BinderInvocation.cs" />
<Compile Include="Helpers\Parser\ConfigurationBinder.cs" /> <Compile Include="Helpers\Parser\ConfigurationBinder.cs" />

View file

@ -22,10 +22,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public List<ParameterSpec> ConstructorParameters { get; } = new(); public List<ParameterSpec> ConstructorParameters { get; } = new();
private string _displayStringWithoutSpecialCharacters;
public string DisplayStringWithoutSpecialCharacters =>
_displayStringWithoutSpecialCharacters ??= $"{MinimalDisplayString.Replace(".", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty)}";
public override bool NeedsMemberBinding => CanInitialize && public override bool NeedsMemberBinding => CanInitialize &&
Properties.Values.Count > 0 && Properties.Values.Count > 0 &&
Properties.Values.Any(p => p.ShouldBind()); Properties.Values.Any(p => p.ShouldBind());

View file

@ -24,7 +24,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
_parseMethodName ??= StringParsableTypeKind is StringParsableTypeKind.ByteArray _parseMethodName ??= StringParsableTypeKind is StringParsableTypeKind.ByteArray
? "ParseByteArray" ? "ParseByteArray"
// MinimalDisplayString.Length is certainly > 2. // MinimalDisplayString.Length is certainly > 2.
: $"Parse{(char.ToUpper(MinimalDisplayString[0]) + MinimalDisplayString.Substring(1)).Replace(".", "")}"; : $"Parse{(char.ToUpper(DisplayString[0]) + DisplayString.Substring(1)).Replace(".", "")}";
return _parseMethodName; return _parseMethodName;
} }

View file

@ -1,21 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
internal sealed record SourceGenerationSpec internal sealed record SourceGenerationSpec
{ {
public Dictionary<Enum, List<InterceptorLocationInfo>> InterceptionInfo { get; } = new();
public ConfigurationBinderInterceptorInfo InterceptionInfo_ConfigBinder { get; } = new();
public Dictionary<MethodsToGen_CoreBindingHelper, HashSet<TypeSpec>> TypesForGen_CoreBindingHelper_Methods { get; } = new(); public Dictionary<MethodsToGen_CoreBindingHelper, HashSet<TypeSpec>> TypesForGen_CoreBindingHelper_Methods { get; } = new();
public Dictionary<MethodsToGen_ConfigurationBinder, HashSet<TypeSpec>> TypesForGen_ConfigurationBinder_BindMethods { get; } = new();
public HashSet<ParsableFromStringSpec> PrimitivesForHelperGen { get; } = new(); public HashSet<ParsableFromStringSpec> PrimitivesForHelperGen { get; } = new();
public HashSet<string> TypeNamespaces { get; } = new() public HashSet<string> Namespaces { get; } = new()
{ {
"System", "System",
"System.CodeDom.Compiler", "System.CodeDom.Compiler",
"System.Globalization", "System.Globalization",
"System.Runtime.CompilerServices",
"Microsoft.Extensions.Configuration", "Microsoft.Extensions.Configuration",
}; };

View file

@ -17,17 +17,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
IsValueType = type.IsValueType; IsValueType = type.IsValueType;
Namespace = type.ContainingNamespace?.ToDisplayString(); Namespace = type.ContainingNamespace?.ToDisplayString();
FullyQualifiedDisplayString = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); DisplayString = type.ToDisplayString(s_minimalDisplayFormat);
MinimalDisplayString = type.ToDisplayString(s_minimalDisplayFormat); Name = Namespace + "." + DisplayString.Replace(".", "+");
Name = Namespace + "." + MinimalDisplayString.Replace(".", "+");
IsInterface = type.TypeKind is TypeKind.Interface; IsInterface = type.TypeKind is TypeKind.Interface;
} }
public string Name { get; } public string Name { get; }
public string FullyQualifiedDisplayString { get; } public string DisplayString { get; }
public string MinimalDisplayString { get; }
public string? Namespace { get; } public string? Namespace { get; }

View file

@ -133,10 +133,10 @@
<value>The collection element type is not supported: '{0}'.</value> <value>The collection element type is not supported: '{0}'.</value>
</data> </data>
<data name="Language VersionIsNotSupportedMessageFormat" xml:space="preserve"> <data name="Language VersionIsNotSupportedMessageFormat" xml:space="preserve">
<value>The project's language version has to be at least 'C# 11'.</value> <value>The project's language version has to be at least 'C# 12'.</value>
</data> </data>
<data name="LanguageVersionIsNotSupportedTitle" xml:space="preserve"> <data name="LanguageVersionIsNotSupportedTitle" xml:space="preserve">
<value>Language version is required to be at least C# 11</value> <value>Language version is required to be at least C# 12</value>
</data> </data>
<data name="MissingPublicInstanceConstructor" xml:space="preserve"> <data name="MissingPublicInstanceConstructor" xml:space="preserve">
<value>Cannot create instance of type '{0}' because it is missing a public instance constructor.</value> <value>Cannot create instance of type '{0}' because it is missing a public instance constructor.</value>

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">Jazyková verze projektu musí být alespoň C# 11</target> <target state="needs-review-translation">Jazyková verze projektu musí být alespoň C# 11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">Verze jazyka musí být alespoň C# 11</target> <target state="needs-review-translation">Verze jazyka musí být alespoň C# 11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">Die Sprachversion des Projekts muss mindestens „C# 11“ sein</target> <target state="needs-review-translation">Die Sprachversion des Projekts muss mindestens „C# 11“ sein</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">Die Sprachversion muss mindestens C# 11 sein</target> <target state="needs-review-translation">Die Sprachversion muss mindestens C# 11 sein</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">La versión del lenguaje del proyecto debe ser al menos "C# 11".</target> <target state="needs-review-translation">La versión del lenguaje del proyecto debe ser al menos "C# 11".</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">La versión del lenguaje debe ser al menos C# 11</target> <target state="needs-review-translation">La versión del lenguaje debe ser al menos C# 11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">La version de langage du projet doit être au moins « C# 11 ».</target> <target state="needs-review-translation">La version de langage du projet doit être au moins « C# 11 ».</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">La version du langage doit être au moins C# 11</target> <target state="needs-review-translation">La version du langage doit être au moins C# 11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">La versione del linguaggio del progetto deve essere almeno 'C# 11'.</target> <target state="needs-review-translation">La versione del linguaggio del progetto deve essere almeno 'C# 11'.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">La versione del linguaggio deve essere almeno C# 11</target> <target state="needs-review-translation">La versione del linguaggio deve essere almeno C# 11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">プロジェクトの言語バージョンは少なくとも 'C# 11' である必要があります。</target> <target state="needs-review-translation">プロジェクトの言語バージョンは少なくとも 'C# 11' である必要があります。</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">言語バージョンは少なくとも C# 11 である必要があります</target> <target state="needs-review-translation">言語バージョンは少なくとも C# 11 である必要があります</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">프로젝트의 언어 버전은 'C# 11' 이상이어야 합니다.</target> <target state="needs-review-translation">프로젝트의 언어 버전은 'C# 11' 이상이어야 합니다.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">언어 버전은 C# 11 이상이어야 합니다.</target> <target state="needs-review-translation">언어 버전은 C# 11 이상이어야 합니다.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">Wersja językowa projektu musi mieć wartość co najmniej „C# 11”.</target> <target state="needs-review-translation">Wersja językowa projektu musi mieć wartość co najmniej „C# 11”.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">Wymagana jest wersja językowa co najmniej C# 11</target> <target state="needs-review-translation">Wymagana jest wersja językowa co najmniej C# 11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">A versão do idioma do projeto deve ser no mínimo 'C# 11'.</target> <target state="needs-review-translation">A versão do idioma do projeto deve ser no mínimo 'C# 11'.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">A versão do idioma deve ser pelo menos C# 11</target> <target state="needs-review-translation">A versão do idioma deve ser pelo menos C# 11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">Версия языка проекта должна быть не ниже "C# 11".</target> <target state="needs-review-translation">Версия языка проекта должна быть не ниже "C# 11".</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">Версия языка должна быть не ниже C# 11</target> <target state="needs-review-translation">Версия языка должна быть не ниже C# 11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">Projenin dil sürümü en az 'C# 11' olmalıdır.</target> <target state="needs-review-translation">Projenin dil sürümü en az 'C# 11' olmalıdır.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">Dil sürümünün en az C# 11 olması gerekir</target> <target state="needs-review-translation">Dil sürümünün en az C# 11 olması gerekir</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">项目的语言版本必须至少为 "C# 11"。</target> <target state="needs-review-translation">项目的语言版本必须至少为 "C# 11"。</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">语言版本必须至少为 C# 11</target> <target state="needs-review-translation">语言版本必须至少为 C# 11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -28,13 +28,13 @@
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Language VersionIsNotSupportedMessageFormat"> <trans-unit id="Language VersionIsNotSupportedMessageFormat">
<source>The project's language version has to be at least 'C# 11'.</source> <source>The project's language version has to be at least 'C# 12'.</source>
<target state="translated">專案的語言版本必須至少為 'C# 11'。</target> <target state="needs-review-translation">專案的語言版本必須至少為 'C# 11'。</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="LanguageVersionIsNotSupportedTitle"> <trans-unit id="LanguageVersionIsNotSupportedTitle">
<source>Language version is required to be at least C# 11</source> <source>Language version is required to be at least C# 12</source>
<target state="translated">語言版本要求至少為 C#11</target> <target state="needs-review-translation">語言版本要求至少為 C#11</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="MissingPublicInstanceConstructor"> <trans-unit id="MissingPublicInstanceConstructor">

View file

@ -8,7 +8,6 @@ using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
#if BUILDING_SOURCE_GENERATOR_TESTS #if BUILDING_SOURCE_GENERATOR_TESTS
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
#endif #endif
@ -2037,6 +2036,7 @@ if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launc
ValidateGeolocation(obj); ValidateGeolocation(obj);
} }
#if !BUILDING_SOURCE_GENERATOR_TESTS
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
public void TraceSwitchTest() public void TraceSwitchTest()
{ {
@ -2056,6 +2056,7 @@ if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launc
Assert.Equal("Info", ts.Value); Assert.Equal("Info", ts.Value);
#endif // NETCOREAPP #endif // NETCOREAPP
} }
#endif
private void ValidateGeolocation(IGeolocation location) private void ValidateGeolocation(IGeolocation location)
{ {

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary> using System;
public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -18,11 +25,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 12, 17)]
public static T? Get<T>(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T));
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClassWithCustomCollections = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "CustomDictionary", "CustomList", "IReadOnlyList", "IReadOnlyDictionary" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClassWithCustomCollections = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "CustomDictionary", "CustomList", "IReadOnlyList", "IReadOnlyDictionary" });
public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions) public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
@ -46,16 +60,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return obj; return obj;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref Program.CustomDictionary<string, int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.CustomDictionary<string, int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -67,11 +76,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.CustomList obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.CustomList obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -83,11 +87,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -99,11 +98,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref ICollection<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref ICollection<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -115,11 +109,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref IReadOnlyList<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref IReadOnlyList<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (obj is not ICollection<int> temp) if (obj is not ICollection<int> temp)
{ {
return; return;
@ -136,11 +125,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -152,11 +136,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref IDictionary<string, int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref IDictionary<string, int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -168,11 +147,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref IReadOnlyDictionary<string, int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref IReadOnlyDictionary<string, int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (obj is not IDictionary<string, int> temp) if (obj is not IDictionary<string, int> temp)
{ {
return; return;
@ -189,11 +163,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClassWithCustomCollections obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClassWithCustomCollections obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions);
if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1) if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1)
@ -229,6 +198,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -281,7 +251,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -298,5 +268,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,18 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary> using System;
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null); using System.CodeDom.Compiler;
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary> [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetBinderOptions(configureOptions)); [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary> {
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration.GetSection(key), ref obj, binderOptions: null); public InterceptsLocationAttribute(string filePath, int line, int column)
{
}
}
} }
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -23,20 +24,72 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); #region IConfiguration extensions.
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) [InterceptsLocationAttribute(@"src-0.cs", 13, 18)]
public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj)
{ {
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (obj is null) if (obj is null)
{ {
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
var typedObj = (Program.MyClass)obj;
BindCore(configuration, ref typedObj, binderOptions: null);
}
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 14, 24)]
public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj, Action<BinderOptions>? configureOptions)
{
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
var typedObj = (Program.MyClass)obj;
BindCore(configuration, ref typedObj, GetBinderOptions(configureOptions));
}
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 15, 24)]
public static void Bind_ProgramMyClass(this IConfiguration configuration, string key, object? obj)
{
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
var typedObj = (Program.MyClass)obj;
BindCore(configuration.GetSection(key), ref typedObj, binderOptions: null);
}
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" });
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -48,11 +101,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -64,11 +112,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null))
@ -81,11 +124,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -120,6 +158,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -163,7 +202,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -180,5 +219,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary> using System;
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -17,20 +24,36 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); #region IConfiguration extensions.
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) [InterceptsLocationAttribute(@"src-0.cs", 12, 20)]
public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj)
{ {
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (obj is null) if (obj is null)
{ {
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
var typedObj = (Program.MyClass)obj;
BindCore(configuration, ref typedObj, binderOptions: null);
}
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" });
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -42,11 +65,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -58,11 +76,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null))
@ -75,11 +88,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -114,6 +122,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -156,5 +165,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary> using System;
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetBinderOptions(configureOptions)); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -17,20 +24,36 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); #region IConfiguration extensions.
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) [InterceptsLocationAttribute(@"src-0.cs", 12, 20)]
public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj, Action<BinderOptions>? configureOptions)
{ {
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (obj is null) if (obj is null)
{ {
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
var typedObj = (Program.MyClass)obj;
BindCore(configuration, ref typedObj, GetBinderOptions(configureOptions));
}
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" });
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -42,11 +65,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -58,11 +76,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null))
@ -75,11 +88,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -114,6 +122,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -157,7 +166,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -174,5 +183,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary> using System;
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration.GetSection(key), ref obj, binderOptions: null); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -17,20 +24,36 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); #region IConfiguration extensions.
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) [InterceptsLocationAttribute(@"src-0.cs", 12, 20)]
public static void Bind_ProgramMyClass(this IConfiguration configuration, string key, object? obj)
{ {
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (obj is null) if (obj is null)
{ {
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
var typedObj = (Program.MyClass)obj;
BindCore(configuration.GetSection(key), ref typedObj, binderOptions: null);
}
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" });
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -42,11 +65,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -58,11 +76,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null))
@ -75,11 +88,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -114,6 +122,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -156,5 +165,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,21 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary> using System;
public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); using System.CodeDom.Compiler;
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary> [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions) ?? default(T)); [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary> {
public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, type, configureOptions: null); public InterceptsLocationAttribute(string filePath, int line, int column)
{
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary> }
public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, type, configureOptions); }
} }
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -26,11 +24,30 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 12, 38)]
public static T? Get<T>(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T));
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 14, 36)]
public static T? Get<T>(this IConfiguration configuration, Action<BinderOptions>? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T));
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 13, 36)]
public static object? Get(this IConfiguration configuration, Type type) => GetCore(configuration, type, configureOptions: null);
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 15, 36)]
public static object? Get(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions) => GetCore(configuration, type, configureOptions);
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" });
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
@ -54,24 +71,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
BindCore(configuration, ref obj, binderOptions); BindCore(configuration, ref obj, binderOptions);
return obj; return obj;
} }
else if (type == typeof(Program.MyClass2))
if (type == typeof(Program.MyClass2))
{ {
var obj = new Program.MyClass2(); var obj = new Program.MyClass2();
BindCore(configuration, ref obj, binderOptions); BindCore(configuration, ref obj, binderOptions);
return obj; return obj;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -83,11 +94,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
var temp2 = new List<int>(); var temp2 = new List<int>();
BindCore(configuration, ref temp2, binderOptions); BindCore(configuration, ref temp2, binderOptions);
int originalCount = obj.Length; int originalCount = obj.Length;
@ -97,11 +103,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -113,11 +114,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -154,11 +150,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
if (configuration["MyInt"] is string value15) if (configuration["MyInt"] is string value15)
@ -167,6 +158,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -219,7 +211,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -236,5 +228,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,21 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary> using System;
public static T? GetValue<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, typeof(T), key) ?? default(T)); using System.CodeDom.Compiler;
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary> [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
public static T? GetValue<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, T defaultValue) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, typeof(T), key) ?? defaultValue); [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary> {
public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, type, key); public InterceptsLocationAttribute(string filePath, int line, int column)
{
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary> }
public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key, object? defaultValue) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, type, key) ?? defaultValue; }
} }
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -25,11 +23,30 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 13, 18)]
public static T? GetValue<T>(this IConfiguration configuration, string key) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? default(T));
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 16, 24)]
public static T? GetValue<T>(this IConfiguration configuration, string key, T defaultValue) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? defaultValue);
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 14, 24)]
public static object? GetValue(this IConfiguration configuration, Type type, string key) => BindingExtensions.GetValueCore(configuration, type, key);
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
[InterceptsLocationAttribute(@"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) public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
{ {
if (configuration is null) if (configuration is null)
@ -48,18 +65,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
return ParseInt(value, () => section.Path); return ParseInt(value, () => section.Path);
} }
else if (type == typeof(bool?))
if (type == typeof(bool?))
{ {
return ParseBool(value, () => section.Path); return ParseBool(value, () => section.Path);
} }
else if (type == typeof(byte[]))
if (type == typeof(byte[]))
{ {
return ParseByteArray(value, () => section.Path); return ParseByteArray(value, () => section.Path);
} }
else if (type == typeof(CultureInfo))
if (type == typeof(CultureInfo))
{ {
return ParseCultureInfo(value, () => section.Path); return ParseCultureInfo(value, () => section.Path);
} }
@ -114,5 +128,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary> using System;
public static T? GetValue<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, typeof(T), key) ?? default(T)); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -16,11 +23,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 10, 20)]
public static T? GetValue<T>(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) public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
{ {
if (configuration is null) if (configuration is null)
@ -54,5 +68,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary> using System;
public static T? GetValue<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, T defaultValue) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, typeof(T), key) ?? defaultValue); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -16,11 +23,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 12, 20)]
public static T? GetValue<T>(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) public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
{ {
if (configuration is null) if (configuration is null)
@ -54,5 +68,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary> using System;
public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, type, key); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -16,11 +23,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
[InterceptsLocationAttribute(@"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) public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
{ {
if (configuration is null) if (configuration is null)
@ -54,5 +68,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary> using System;
public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key, object? defaultValue) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, type, key) ?? defaultValue; 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -16,11 +23,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
[InterceptsLocationAttribute(@"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) public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
{ {
if (configuration is null) if (configuration is null)
@ -54,5 +68,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary> using System;
public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -17,11 +24,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 11, 40)]
public static T? Get<T>(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T));
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" });
public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions) public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
@ -45,16 +59,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return obj; return obj;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -66,11 +75,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
var temp1 = new List<int>(); var temp1 = new List<int>();
BindCore(configuration, ref temp1, binderOptions); BindCore(configuration, ref temp1, binderOptions);
int originalCount = obj.Length; int originalCount = obj.Length;
@ -80,11 +84,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -96,11 +95,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -135,6 +129,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -187,7 +182,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -204,5 +199,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary> using System;
public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions) ?? default(T)); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -17,11 +24,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 11, 40)]
public static T? Get<T>(this IConfiguration configuration, Action<BinderOptions>? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T));
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" });
public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions) public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
@ -45,16 +59,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return obj; return obj;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -66,11 +75,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
var temp1 = new List<int>(); var temp1 = new List<int>();
BindCore(configuration, ref temp1, binderOptions); BindCore(configuration, ref temp1, binderOptions);
int originalCount = obj.Length; int originalCount = obj.Length;
@ -80,11 +84,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -96,11 +95,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -135,6 +129,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -187,7 +182,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -204,5 +199,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary> using System;
public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, type, configureOptions: null); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -17,11 +24,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 11, 40)]
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<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions) public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
@ -45,16 +59,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return obj; return obj;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
if (configuration["MyInt"] is string value1) if (configuration["MyInt"] is string value1)
@ -63,6 +72,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -115,7 +125,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -132,5 +142,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary> using System;
public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, type, configureOptions); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -17,11 +24,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IConfiguration extensions.
/// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 11, 20)]
public static object? Get(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions) => GetCore(configuration, type, configureOptions);
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions) public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
@ -45,16 +59,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return obj; return obj;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
if (configuration["MyInt"] is string value1) if (configuration["MyInt"] is string value1)
@ -63,6 +72,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -115,7 +125,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -132,5 +142,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,31 +2,18 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedOptionsBuilderBinder
{ {
/// <summary>Registers the dependency injection container to bind <typeparamref name="TOptions"/> against the <see cref="global::Microsoft.Extensions.Configuration.IConfiguration"/> obtained from the DI service provider.</summary> using System;
public static global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> BindConfiguration<TOptions>(this global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> optionsBuilder, string configSectionPath, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions = null) where TOptions : class using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{ {
if (optionsBuilder is null) public InterceptsLocationAttribute(string filePath, int line, int column)
{ {
throw new global::System.ArgumentNullException(nameof(optionsBuilder));
} }
if (configSectionPath is null)
{
throw new global::System.ArgumentNullException(nameof(configSectionPath));
}
optionsBuilder.Configure<global::Microsoft.Extensions.Configuration.IConfiguration>((obj, configuration) =>
{
global::Microsoft.Extensions.Configuration.IConfiguration section = string.Equals(string.Empty, configSectionPath, global::System.StringComparison.OrdinalIgnoreCase) ? configuration : configuration.GetSection(configSectionPath);
global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(section, obj, typeof(TOptions), configureOptions);
});
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IOptionsChangeTokenSource<TOptions>, global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource<TOptions>>(optionsBuilder.Services);
return optionsBuilder;
} }
} }
@ -34,31 +21,74 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region OptionsBuilder<TOptions> extensions.
/// <summary>Registers the dependency injection container to bind <typeparamref name="TOptions"/> against the <see cref="IConfiguration"/> obtained from the DI service provider.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 12, 24)]
public static OptionsBuilder<TOptions> BindConfiguration<TOptions>(this OptionsBuilder<TOptions> optionsBuilder, string configSectionPath, Action<BinderOptions>? configureOptions = null) where TOptions : class
{
if (optionsBuilder is null)
{
throw new ArgumentNullException(nameof(optionsBuilder));
}
if (configSectionPath is null)
{
throw new ArgumentNullException(nameof(configSectionPath));
}
optionsBuilder.Configure<IConfiguration>((obj, configuration) =>
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
IConfiguration section = string.Equals(string.Empty, configSectionPath, StringComparison.OrdinalIgnoreCase) ? configuration : configuration.GetSection(configSectionPath);
BindCoreMain(section, obj, typeof(TOptions), configureOptions);
});
optionsBuilder.Services.AddSingleton<IOptionsChangeTokenSource<TOptions>, ConfigurationChangeTokenSource<TOptions>>();
return optionsBuilder;
}
#endregion OptionsBuilder<TOptions> extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" });
public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions) public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
{ {
if (configuration is null) if (configuration is null)
{ {
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions); if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (!HasValueOrChildren(configuration)) if (!HasValueOrChildren(configuration))
{ {
return; return;
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions);
if (type == typeof(Program.MyClass)) if (type == typeof(Program.MyClass))
{ {
var temp = (Program.MyClass)obj; var temp = (Program.MyClass)obj;
@ -66,16 +96,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -87,11 +112,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -110,6 +130,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -162,7 +183,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -179,5 +200,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,49 +2,18 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedOptionsBuilderBinder
{ {
/// <summary>Registers a configuration instance which <typeparamref name="TOptions"/> will bind against.</summary> using System;
public static global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> Bind<TOptions>(this global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> optionsBuilder, global::Microsoft.Extensions.Configuration.IConfiguration configuration) where TOptions : class using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{ {
return global::GeneratedOptionsBuilderBinder.Bind(optionsBuilder, configuration, configureOptions: null); public InterceptsLocationAttribute(string filePath, int line, int column)
}
/// <summary>Registers a configuration instance which <typeparamref name="TOptions"/> will bind against.</summary>
public static global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> Bind<TOptions>(this global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> optionsBuilder, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class
{
if (optionsBuilder is null)
{ {
throw new global::System.ArgumentNullException(nameof(optionsBuilder));
} }
global::GeneratedServiceCollectionBinder.Configure<TOptions>(optionsBuilder.Services, optionsBuilder.Name, configuration, configureOptions);
return optionsBuilder;
}
}
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedServiceCollectionBinder
{
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<TOptions>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{
throw new global::System.ArgumentNullException(nameof(services));
}
if (configuration is null)
{
throw new global::System.ArgumentNullException(nameof(configuration));
}
global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services);
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IOptionsChangeTokenSource<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource<TOptions>(name, configuration));
return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions)));
} }
} }
@ -52,31 +21,79 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region OptionsBuilder<TOptions> extensions.
/// <summary>Registers a configuration instance which <typeparamref name="TOptions"/> will bind against.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 15, 24)]
public static OptionsBuilder<TOptions> Bind<TOptions>(this OptionsBuilder<TOptions> optionsBuilder, IConfiguration configuration) where TOptions : class
{
return Bind(optionsBuilder, configuration, configureOptions: null);
}
/// <summary>Registers a configuration instance which <typeparamref name="TOptions"/> will bind against.</summary>
public static OptionsBuilder<TOptions> Bind<TOptions>(this OptionsBuilder<TOptions> optionsBuilder, IConfiguration configuration, Action<BinderOptions>? configureOptions) where TOptions : class
{
if (optionsBuilder is null)
{
throw new ArgumentNullException(nameof(optionsBuilder));
}
Configure<TOptions>(optionsBuilder.Services, optionsBuilder.Name, configuration, configureOptions);
return optionsBuilder;
}
#endregion OptionsBuilder<TOptions> extensions.
#region IServiceCollection extensions.
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string? name, IConfiguration configuration, Action<BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
OptionsServiceCollectionExtensions.AddOptions(services);
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, configuration));
return services.AddSingleton<Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(new Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions)));
}
#endregion IServiceCollection extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" });
public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions) public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
{ {
if (configuration is null) if (configuration is null)
{ {
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions); if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (!HasValueOrChildren(configuration)) if (!HasValueOrChildren(configuration))
{ {
return; return;
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions);
if (type == typeof(Program.MyClass)) if (type == typeof(Program.MyClass))
{ {
var temp = (Program.MyClass)obj; var temp = (Program.MyClass)obj;
@ -84,16 +101,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -105,11 +117,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -128,6 +135,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -180,7 +188,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -197,5 +205,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,43 +2,18 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedOptionsBuilderBinder
{ {
/// <summary>Registers a configuration instance which <typeparamref name="TOptions"/> will bind against.</summary> using System;
public static global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> Bind<TOptions>(this global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> optionsBuilder, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{ {
if (optionsBuilder is null) public InterceptsLocationAttribute(string filePath, int line, int column)
{ {
throw new global::System.ArgumentNullException(nameof(optionsBuilder));
} }
global::GeneratedServiceCollectionBinder.Configure<TOptions>(optionsBuilder.Services, optionsBuilder.Name, configuration, configureOptions);
return optionsBuilder;
}
}
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedServiceCollectionBinder
{
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<TOptions>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{
throw new global::System.ArgumentNullException(nameof(services));
}
if (configuration is null)
{
throw new global::System.ArgumentNullException(nameof(configuration));
}
global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services);
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IOptionsChangeTokenSource<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource<TOptions>(name, configuration));
return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions)));
} }
} }
@ -46,31 +21,73 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region OptionsBuilder<TOptions> extensions.
/// <summary>Registers a configuration instance which <typeparamref name="TOptions"/> will bind against.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 15, 24)]
public static OptionsBuilder<TOptions> Bind<TOptions>(this OptionsBuilder<TOptions> optionsBuilder, IConfiguration configuration, Action<BinderOptions>? configureOptions) where TOptions : class
{
if (optionsBuilder is null)
{
throw new ArgumentNullException(nameof(optionsBuilder));
}
Configure<TOptions>(optionsBuilder.Services, optionsBuilder.Name, configuration, configureOptions);
return optionsBuilder;
}
#endregion OptionsBuilder<TOptions> extensions.
#region IServiceCollection extensions.
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string? name, IConfiguration configuration, Action<BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
OptionsServiceCollectionExtensions.AddOptions(services);
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, configuration));
return services.AddSingleton<Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(new Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions)));
}
#endregion IServiceCollection extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" });
public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions) public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
{ {
if (configuration is null) if (configuration is null)
{ {
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions); if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (!HasValueOrChildren(configuration)) if (!HasValueOrChildren(configuration))
{ {
return; return;
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions);
if (type == typeof(Program.MyClass)) if (type == typeof(Program.MyClass))
{ {
var temp = (Program.MyClass)obj; var temp = (Program.MyClass)obj;
@ -78,16 +95,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -99,11 +111,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -122,6 +129,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -174,7 +182,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -191,5 +199,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,12 +2,19 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{ {
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary> using System;
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null); 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 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@ -17,20 +24,36 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(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" }); #region IConfiguration extensions.
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) [InterceptsLocationAttribute(@"src-0.cs", 13, 16)]
public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj)
{ {
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (obj is null) if (obj is null)
{ {
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
var typedObj = (Program.MyClass)obj;
BindCore(configuration, ref typedObj, binderOptions: null);
}
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Prop0", "Prop1", "Prop2", "Prop3", "Prop4", "Prop5", "Prop6", "Prop8", "Prop9", "Prop10", "Prop13", "Prop14", "Prop15", "Prop16", "Prop17", "Prop19", "Prop20", "Prop21", "Prop23", "Prop24", "Prop25", "Prop26", "Prop27", "Prop7", "Prop11", "Prop12", "Prop18", "Prop22" });
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
if (configuration["Prop0"] is string value0) if (configuration["Prop0"] is string value0)
@ -168,6 +191,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -517,5 +541,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,64 +2,84 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedServiceCollectionBinder
{ {
/// <summary>Registers a configuration instance which TOptions will bind against.</summary> using System;
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<TOptions>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration) where TOptions : class using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{ {
return global::GeneratedServiceCollectionBinder.Configure<TOptions>(services, string.Empty, configuration, configureOptions: null); public InterceptsLocationAttribute(string filePath, int line, int column)
}
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<TOptions>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{ {
throw new global::System.ArgumentNullException(nameof(services));
} }
if (configuration is null)
{
throw new global::System.ArgumentNullException(nameof(configuration));
}
global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services);
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IOptionsChangeTokenSource<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource<TOptions>(name, configuration));
return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions)));
} }
} }
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IServiceCollection extensions.
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 14, 18)]
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration configuration) where TOptions : class
{
return Configure<TOptions>(services, string.Empty, configuration, configureOptions: null);
}
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string? name, IConfiguration configuration, Action<BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
OptionsServiceCollectionExtensions.AddOptions(services);
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, configuration));
return services.AddSingleton<Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(new Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions)));
}
#endregion IServiceCollection extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" });
public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions) public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
{ {
if (configuration is null) if (configuration is null)
{ {
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions); if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (!HasValueOrChildren(configuration)) if (!HasValueOrChildren(configuration))
{ {
return; return;
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions);
if (type == typeof(Program.MyClass)) if (type == typeof(Program.MyClass))
{ {
var temp = (Program.MyClass)obj; var temp = (Program.MyClass)obj;
@ -67,16 +87,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -88,11 +103,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
if (configuration["MyInt"] is string value1) if (configuration["MyInt"] is string value1)
@ -103,11 +113,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref List<Program.MyClass2> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<Program.MyClass2> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
var value = new Program.MyClass2(); var value = new Program.MyClass2();
@ -118,11 +123,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -134,11 +134,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -173,6 +168,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -225,7 +221,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -242,5 +238,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,64 +2,84 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedServiceCollectionBinder
{ {
/// <summary>Registers a configuration instance which TOptions will bind against.</summary> using System;
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<TOptions>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{ {
return global::GeneratedServiceCollectionBinder.Configure<TOptions>(services, string.Empty, configuration, configureOptions); public InterceptsLocationAttribute(string filePath, int line, int column)
}
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<TOptions>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{ {
throw new global::System.ArgumentNullException(nameof(services));
} }
if (configuration is null)
{
throw new global::System.ArgumentNullException(nameof(configuration));
}
global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services);
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IOptionsChangeTokenSource<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource<TOptions>(name, configuration));
return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions)));
} }
} }
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IServiceCollection extensions.
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 14, 18)]
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration configuration, Action<BinderOptions>? configureOptions) where TOptions : class
{
return Configure<TOptions>(services, string.Empty, configuration, configureOptions);
}
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string? name, IConfiguration configuration, Action<BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
OptionsServiceCollectionExtensions.AddOptions(services);
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, configuration));
return services.AddSingleton<Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(new Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions)));
}
#endregion IServiceCollection extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" });
public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions) public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
{ {
if (configuration is null) if (configuration is null)
{ {
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions); if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (!HasValueOrChildren(configuration)) if (!HasValueOrChildren(configuration))
{ {
return; return;
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions);
if (type == typeof(Program.MyClass)) if (type == typeof(Program.MyClass))
{ {
var temp = (Program.MyClass)obj; var temp = (Program.MyClass)obj;
@ -67,16 +87,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -88,11 +103,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
if (configuration["MyInt"] is string value1) if (configuration["MyInt"] is string value1)
@ -103,11 +113,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref List<Program.MyClass2> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<Program.MyClass2> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
var value = new Program.MyClass2(); var value = new Program.MyClass2();
@ -118,11 +123,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -134,11 +134,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -173,6 +168,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -225,7 +221,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -242,5 +238,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,64 +2,84 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedServiceCollectionBinder
{ {
/// <summary>Registers a configuration instance which TOptions will bind against.</summary> using System;
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<TOptions>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration) where TOptions : class using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{ {
return global::GeneratedServiceCollectionBinder.Configure<TOptions>(services, name, configuration, configureOptions: null); public InterceptsLocationAttribute(string filePath, int line, int column)
}
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<TOptions>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{ {
throw new global::System.ArgumentNullException(nameof(services));
} }
if (configuration is null)
{
throw new global::System.ArgumentNullException(nameof(configuration));
}
global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services);
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IOptionsChangeTokenSource<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource<TOptions>(name, configuration));
return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions)));
} }
} }
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IServiceCollection extensions.
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 14, 18)]
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string? name, IConfiguration configuration) where TOptions : class
{
return Configure<TOptions>(services, name, configuration, configureOptions: null);
}
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string? name, IConfiguration configuration, Action<BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
OptionsServiceCollectionExtensions.AddOptions(services);
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, configuration));
return services.AddSingleton<Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(new Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions)));
}
#endregion IServiceCollection extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" });
public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions) public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
{ {
if (configuration is null) if (configuration is null)
{ {
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions); if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (!HasValueOrChildren(configuration)) if (!HasValueOrChildren(configuration))
{ {
return; return;
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions);
if (type == typeof(Program.MyClass)) if (type == typeof(Program.MyClass))
{ {
var temp = (Program.MyClass)obj; var temp = (Program.MyClass)obj;
@ -67,16 +87,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -88,11 +103,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
if (configuration["MyInt"] is string value1) if (configuration["MyInt"] is string value1)
@ -103,11 +113,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref List<Program.MyClass2> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<Program.MyClass2> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
var value = new Program.MyClass2(); var value = new Program.MyClass2();
@ -118,11 +123,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -134,11 +134,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -173,6 +168,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -225,7 +221,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -242,5 +238,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -2,58 +2,78 @@
#nullable enable #nullable enable
#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary> namespace System.Runtime.CompilerServices
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedServiceCollectionBinder
{ {
/// <summary>Registers a configuration instance which TOptions will bind against.</summary> using System;
public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<TOptions>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{ {
if (services is null) public InterceptsLocationAttribute(string filePath, int line, int column)
{ {
throw new global::System.ArgumentNullException(nameof(services));
} }
if (configuration is null)
{
throw new global::System.ArgumentNullException(nameof(configuration));
}
global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services);
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IOptionsChangeTokenSource<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource<TOptions>(name, configuration));
return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton<global::Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions)));
} }
} }
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{ {
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper file static class BindingExtensions
{ {
#region IServiceCollection extensions.
/// <summary>Registers a configuration instance which TOptions will bind against.</summary>
[InterceptsLocationAttribute(@"src-0.cs", 14, 18)]
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string? name, IConfiguration configuration, Action<BinderOptions>? configureOptions) where TOptions : class
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
OptionsServiceCollectionExtensions.AddOptions(services);
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, configuration));
return services.AddSingleton<Microsoft.Extensions.Options.IConfigureOptions<TOptions>>(new Microsoft.Extensions.Options.ConfigureNamedOptions<TOptions>(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions)));
}
#endregion IServiceCollection extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" });
public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions) public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
{ {
if (configuration is null) if (configuration is null)
{ {
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions); if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (!HasValueOrChildren(configuration)) if (!HasValueOrChildren(configuration))
{ {
return; return;
} }
BinderOptions? binderOptions = GetBinderOptions(configureOptions);
if (type == typeof(Program.MyClass)) if (type == typeof(Program.MyClass))
{ {
var temp = (Program.MyClass)obj; var temp = (Program.MyClass)obj;
@ -61,16 +81,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
return; return;
} }
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
} }
public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -82,11 +97,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
if (configuration["MyInt"] is string value1) if (configuration["MyInt"] is string value1)
@ -97,11 +107,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref List<Program.MyClass2> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref List<Program.MyClass2> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
var value = new Program.MyClass2(); var value = new Program.MyClass2();
@ -112,11 +117,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
foreach (IConfigurationSection section in configuration.GetChildren()) foreach (IConfigurationSection section in configuration.GetChildren())
{ {
if (section.Value is string value) if (section.Value is string value)
@ -128,11 +128,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
{ {
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
obj.MyString = configuration["MyString"]!; obj.MyString = configuration["MyString"]!;
@ -167,6 +162,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
} }
} }
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary> /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions) public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
{ {
@ -219,7 +215,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
if (binderOptions.BindNonPublicProperties) if (binderOptions.BindNonPublicProperties)
{ {
throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
} }
return binderOptions; return binderOptions;
@ -236,5 +232,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
} }
} }
#endregion Core binding extensions.
} }
} }

View file

@ -8,6 +8,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
{ {
public partial class ConfigurationBindingGeneratorTests public partial class ConfigurationBindingGeneratorTests
{ {
#region IServiceCollection extensions.
private string GetConfigureSource(string paramList) => $$""" private string GetConfigureSource(string paramList) => $$"""
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -40,7 +41,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
} }
} }
"""; """;
#endregion IServiceCollection extensions.
#region OptionsBuilder<T> extensions.
[Fact] [Fact]
public async Task Configure_T() => public async Task Configure_T() =>
await VerifyAgainstBaselineUsingFile("Configure_T.generated.txt", GetConfigureSource("section"), extType: ExtensionClassType.ServiceCollection); await VerifyAgainstBaselineUsingFile("Configure_T.generated.txt", GetConfigureSource("section"), extType: ExtensionClassType.ServiceCollection);
@ -126,5 +129,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
await VerifyAgainstBaselineUsingFile("BindConfiguration.generated.txt", GetSource(), extType: ExtensionClassType.OptionsBuilder); await VerifyAgainstBaselineUsingFile("BindConfiguration.generated.txt", GetSource(), extType: ExtensionClassType.OptionsBuilder);
await VerifyAgainstBaselineUsingFile("BindConfiguration.generated.txt", GetSource(@", _ => { }"), extType: ExtensionClassType.OptionsBuilder); await VerifyAgainstBaselineUsingFile("BindConfiguration.generated.txt", GetSource(@", _ => { }"), extType: ExtensionClassType.OptionsBuilder);
} }
#endregion OptionsBuilder<T> extensions.
} }
} }

View file

@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using System; using System;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@ -44,7 +43,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
[Theory] [Theory]
[InlineData(LanguageVersion.Preview)] [InlineData(LanguageVersion.Preview)]
[InlineData(LanguageVersion.CSharp11)]
public async Task Bind(LanguageVersion langVersion) => public async Task Bind(LanguageVersion langVersion) =>
await VerifyAgainstBaselineUsingFile("Bind.generated.txt", BindCallSampleCode, langVersion, extType: ExtensionClassType.ConfigurationBinder); await VerifyAgainstBaselineUsingFile("Bind.generated.txt", BindCallSampleCode, langVersion, extType: ExtensionClassType.ConfigurationBinder);
@ -651,7 +649,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
await VerifyAgainstBaselineUsingFile("Collections.generated.txt", source, assessDiagnostics: (d) => await VerifyAgainstBaselineUsingFile("Collections.generated.txt", source, assessDiagnostics: (d) =>
{ {
Console.WriteLine((d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count() , d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count())); Console.WriteLine((d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count(), d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()));
Assert.Equal(3, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); Assert.Equal(3, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count());
Assert.Equal(6, d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()); Assert.Equal(6, d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count());
}); });

View file

@ -52,15 +52,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
ServiceCollection, ServiceCollection,
} }
[Fact] [Theory]
public async Task LangVersionMustBeCharp11OrHigher() [InlineData(LanguageVersion.CSharp11)]
[InlineData(LanguageVersion.CSharp10)]
public async Task LangVersionMustBeCharp12OrHigher(LanguageVersion langVersion)
{ {
var (d, r) = await RunGenerator(BindCallSampleCode, LanguageVersion.CSharp10); var (d, r) = await RunGenerator(BindCallSampleCode, langVersion);
Assert.Empty(r); Assert.Empty(r);
Diagnostic diagnostic = Assert.Single(d); Diagnostic diagnostic = Assert.Single(d);
Assert.True(diagnostic.Id == "SYSLIB1102"); Assert.True(diagnostic.Id == "SYSLIB1102");
Assert.Contains("C# 11", diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); Assert.Contains("C# 12", diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture));
Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity);
} }
@ -250,9 +252,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
Assert.Single(r); Assert.Single(r);
string generatedSource = string.Join('\n', r[0].SourceText.Lines.Select(x => x.ToString())); string generatedSource = string.Join('\n', r[0].SourceText.Lines.Select(x => x.ToString()));
Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass0 obj) => {{ }};", generatedSource); Assert.Contains("public static void Bind_ProgramMyClass0(this IConfiguration configuration, object? obj)", generatedSource);
Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass1 obj, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => {{ }};", generatedSource); Assert.Contains("public static void Bind_ProgramMyClass1(this IConfiguration configuration, object? obj, Action<BinderOptions>? configureOptions)", generatedSource);
Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass2 obj) => {{ }};", generatedSource); Assert.Contains("public static void Bind_ProgramMyClass2(this IConfiguration configuration, string key, object? obj)", generatedSource);
Assert.Empty(d); Assert.Empty(d);
} }
@ -395,7 +397,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
private static async Task<(ImmutableArray<Diagnostic>, ImmutableArray<GeneratedSourceResult>)> RunGenerator( private static async Task<(ImmutableArray<Diagnostic>, ImmutableArray<GeneratedSourceResult>)> RunGenerator(
string testSourceCode, string testSourceCode,
LanguageVersion langVersion = LanguageVersion.CSharp11, LanguageVersion langVersion = LanguageVersion.Preview,
IEnumerable<Assembly>? references = null) => IEnumerable<Assembly>? references = null) =>
await RoslynTestUtils.RunGenerator( await RoslynTestUtils.RunGenerator(
new ConfigurationBindingGenerator(), new ConfigurationBindingGenerator(),

View file

@ -4,7 +4,7 @@
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<!-- Type not supported; property on type not suppported --> <!-- Type not supported; property on type not suppported -->
<NoWarn>SYSLIB1100,SYSLIB1101</NoWarn> <NoWarn>SYSLIB1100,SYSLIB1101</NoWarn>
<!-- The SDK disables the configuration binding generator by default no matter how it was referenced, so we need to enable it here for testing. --> <Features>$(Features);InterceptorsPreview</Features>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator> <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup> </PropertyGroup>

View file

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks> <TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems> <EnableDefaultItems>true</EnableDefaultItems>
<Features>$(Features);InterceptorsPreview</Features>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator> <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<PackageDescription>Configuration support for Microsoft.Extensions.Logging.</PackageDescription> <PackageDescription>Configuration support for Microsoft.Extensions.Logging.</PackageDescription>

View file

@ -7,7 +7,9 @@
<DefineConstants>$(DefineConstants);NO_SUPPRESS_GC_TRANSITION</DefineConstants> <DefineConstants>$(DefineConstants);NO_SUPPRESS_GC_TRANSITION</DefineConstants>
<IncludePlatformAttributes>true</IncludePlatformAttributes> <IncludePlatformAttributes>true</IncludePlatformAttributes>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<Features>$(Features);InterceptorsPreview</Features>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator> <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<PackageDescription>Console logger provider implementation for Microsoft.Extensions.Logging.</PackageDescription> <PackageDescription>Console logger provider implementation for Microsoft.Extensions.Logging.</PackageDescription>
</PropertyGroup> </PropertyGroup>

View file

@ -2,9 +2,9 @@
<PropertyGroup> <PropertyGroup>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks> <TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<DefineConstants>$(DefineConstants);BUILDING_SOURCE_GENERATOR_TESTS;ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER</DefineConstants> <DefineConstants>$(DefineConstants);BUILDING_SOURCE_GENERATOR_TESTS;ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER</DefineConstants>
<!-- The SDK disables the configuration binding generator by default no matter how it was referenced, so we need to enable it here for testing. --> <Features>$(Features);InterceptorsPreview</Features>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator> <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup> </PropertyGroup>