Closed
Description
dotnet build
on the following projects results in the generated source from the Configuration.Binder source generator to not compile.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
</ItemGroup>
</Project>
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Configuration.Bind(new CertificateDescription());
var host = builder.Build();
host.Run();
public enum CredentialSource
{
Certificate = 0,
KeyVault = 1,
Base64Encoded = 2,
}
public enum CertificateSource
{
Certificate = 0,
KeyVault = 1,
}
public class CredentialDescription
{
public CredentialSource SourceType { get; set; }
}
public class CertificateDescription : CredentialDescription
{
// make a new property that limits the SourceType to CertificateSource
public new CertificateSource SourceType
{
get { return (CertificateSource)base.SourceType; }
set { base.SourceType = (CredentialSource)value; }
}
}
Expected result
I expect the code to compile.
Actual result
Build FAILED.
C:\Users\eerhardt\source\repos\WorkerService20\WorkerService20\obj\Debug\net8.0\Microsoft.Extensions.Configuration.Binder.SourceGeneration\Microsoft.
Extensions.Configuration.Binder.SourceGeneration.ConfigurationBindingGenerator\BindingExtensions.g.cs(65,39): error CS0266: Cannot implicitly convert
type 'CredentialSource' to 'CertificateSource'. An explicit conversion exists (are you missing a cast?) [C:\Users\eerhardt\source\repos\WorkerServic
e20\WorkerService20\WorkerService20.csproj]
0 Warning(s)
1 Error(s)
The problematic line is:
global::CertificateDescription instance;
instance.SourceType = ParseEnum<global::CredentialSource>(value0, () => configuration.GetSection("SourceType").Path);
instance.SourceType
is a CertificateSource
, but the code is parsing as a CredentialSource
.
Generated code
// <auto-generated/>
#nullable enable annotations
#nullable disable warnings
// Suppress warnings about [Obsolete] member usage in generated code.
#pragma warning disable CS0612, CS0618
namespace System.Runtime.CompilerServices
{
using System;
using System.CodeDom.Compiler;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "8.0.9.7805")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string filePath, int line, int column)
{
}
}
}
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
using Microsoft.Extensions.Configuration;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "8.0.9.7805")]
file static class BindingExtensions
{
#region IConfiguration extensions.
/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
[InterceptsLocation(@"C:\Users\eerhardt\source\repos\WorkerService20\WorkerService20\Program.cs", 6, 23)]
public static void Bind_CertificateDescription(this IConfiguration configuration, object? instance)
{
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (instance is null)
{
return;
}
var typedObj = (global::CertificateDescription)instance;
BindCore(configuration, ref typedObj, defaultValueIfNotFound: false, binderOptions: null);
}
#endregion IConfiguration extensions.
#region Core binding extensions.
private readonly static Lazy<HashSet<string>> s_configKeys_CertificateDescription = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "SourceType" });
public static void BindCore(IConfiguration configuration, ref global::CertificateDescription instance, bool defaultValueIfNotFound, BinderOptions? binderOptions)
{
ValidateConfigurationKeys(typeof(global::CertificateDescription), s_configKeys_CertificateDescription, configuration, binderOptions);
if (configuration["SourceType"] is string value0)
{
instance.SourceType = ParseEnum<global::CredentialSource>(value0, () => configuration.GetSection("SourceType").Path);
}
else if (defaultValueIfNotFound)
{
instance.SourceType = default;
}
}
/// <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)
{
if (binderOptions?.ErrorOnUnknownConfiguration is true)
{
List<string>? temp = null;
foreach (IConfigurationSection section in configuration.GetChildren())
{
if (!keys.Value.Contains(section.Key))
{
(temp ??= new List<string>()).Add($"'{section.Key}'");
}
}
if (temp is not null)
{
throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
}
}
}
public static T ParseEnum<T>(string value, Func<string?> getPath) where T : struct
{
try
{
#if NETFRAMEWORK || NETSTANDARD2_0
return (T)Enum.Parse(typeof(T), value, ignoreCase: true);
#else
return Enum.Parse<T>(value, ignoreCase: true);
#endif
}
catch (Exception exception)
{
throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(T)}'.", exception);
}
}
#endregion Core binding extensions.
}
}