-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Open
Labels
area-Extensions-Configurationsource-generatorIndicates an issue with a source generator featureIndicates an issue with a source generator feature
Milestone
Description
Description
Binding fails with InvalidOperationException for an IEnumerable<string> field in a nested record:
internal sealed record Config
{
public Source? Source { get; set; }
}
internal sealed record Source(string Name, IEnumerable<string> Addresses); // Fails on binding of AddressesReproduction Steps
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
// Simulated configuration file content
var config = """
{
"source": {
"name": "DemoService",
"addresses": [ "127.0.0.1" ]
}
}
""";
// Steps to reproduce
var builder = Host.CreateApplicationBuilder(args);
builder.Configuration.AddJsonStream(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(config)));
builder.Services.AddSingleton<DemoService>();
builder.Services.Configure<Config>(builder.Configuration);
var app = builder.Build();
await app.Services.GetRequiredService<DemoService>().StartAsync(new CancellationToken());
app.Run();
// Configuration structure that fails on binding
internal sealed record Config
{
public Source? Source { get; set; }
}
internal sealed record Source(string Name, IEnumerable<string> Addresses);
// Background service that uses the configuration
internal class DemoService(IOptionsMonitor<Config> config) : BackgroundService
{
internal string Name { get; } = config.CurrentValue.Source?.Name ?? "DefaultName";
// This line throws an InvalidOperationException during binding
internal IEnumerable<string> Addresses { get; } = config.CurrentValue.Source?.Addresses ?? [];
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Delay(Timeout.Infinite, stoppingToken);
}
}Expected behavior
Config.Source.Addresses is bound correctly.
Actual behavior
Accessing Config.Source?.Addresses fails with InvalidOperationException:
Here is a snipped from the generated file:
global::System.Collections.Generic.IEnumerable<string> Addresses = default!;
var value5 = configuration.GetSection("Addresses");
if (AsConfigWithChildren(value5) is IConfigurationSection section6)
{
Addresses = (global::System.Collections.Generic.IEnumerable<string>)new List<string>();
BindCore(section6, ref Addresses, defaultValueIfNotFound: false, binderOptions);
}
if (Addresses is null && TryGetConfigurationValue(value5, key: null, out string? value9) && value9 == string.Empty)
{
Addresses = global::System.Array.Empty<string>();
}
else
{
throw new InvalidOperationException("Cannot create instance of type 'Source' because parameter 'Addresses' has no matching config. Each parameter in the constructor that does not have a default value must have a corresponding config entry.");
}The exception is thrown from the else statement.
Regression?
The same code worked with Microsoft.Extensions.Hosting NuGET in versions 9.0.12 and 10.0.0
Apparently, the newly generated code is
if (Addresses is null && TryGetConfigurationValue(value5, key: null, out string? value9) && value9 == string.Empty)
{
Addresses = global::System.Array.Empty<string>();
}It was not generated by version 10.0.0 of the NuGET
Known Workarounds
No response
Configuration
.NET10 (10.0.2)
Windows 11 Pro 25H2 Build 26200.7623
x64
Other information
No response
Metadata
Metadata
Assignees
Labels
area-Extensions-Configurationsource-generatorIndicates an issue with a source generator featureIndicates an issue with a source generator feature