dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.28k stars 4.73k forks source link

ISet in .NET 8 not populating from configuration as expected in .NET 6 #102926

Open senkadir opened 5 months ago

senkadir commented 5 months ago

Describe the bug

ISet in .NET 8 not populating from configuration as expected in .NET 6

I have following option definition:

using System.Collections.Generic;

namespace WebApplication3;

public record TestOptions
{
   public ISet<Uri> ISetTest { get; set; } = new HashSet<Uri>();
}

This option class registered in the Startup.cs as usual and In .Net 6 ISetTest property contains values but in .Net 8 it's empty.

Workaround

Use HashSet instead of ISet:

public HashSet<Uri> ISetTest { get; set; } = new HashSet<Uri>();

To Reproduce

Create ASP.NET Core Web API project with .Net 8 LTS.

Create option class:

public record TestOptions
{
    public ISet<Uri> ISetTest { get; set; } = new HashSet<Uri>();
}

Add settings to appsetting.json:

"Tests": {
    "ISetTest": [ "https://testuri.net" ]
}

Register options in Startup.cs or Program.cs:

builder.Services.Configure<TestOptions>(builder.Configuration.GetSection("Tests"));

Resolve TestOptions from a controller:

public WeatherForecastController(IOptions<TestOptions> options)
{
}

Then start the application in debug mode and set a breakpoint on the options variable to check its value. The result should look like:

For .Net 6:

image

For .Net 8:

image

Exceptions (if any)

No exception.

Further technical details

Runtime Environment: OS Name: Windows OS Version: 10.0.19045 OS Platform: Windows RID: win-x64 Base Path: C:\Program Files\dotnet\sdk\8.0.105\

.NET workloads installed: Workload version: 8.0.100-manifests.c1c70047 [maui] Installation Source: SDK 8.0.100 Manifest Version: 8.0.3/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.maui\8.0.3\WorkloadManifest.json Install Type: Msi

[maccatalyst] Installation Source: VS 17.8.34322.80 Manifest Version: 17.0.8478/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.maccatalyst\17.0.8478\WorkloadManifest.json Install Type: Msi

[ios] Installation Source: VS 17.8.34322.80 Manifest Version: 17.0.8478/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.ios\17.0.8478\WorkloadManifest.json Install Type: Msi

[maui-windows] Installation Source: VS 17.8.34322.80 Manifest Version: 8.0.3/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.maui\8.0.3\WorkloadManifest.json Install Type: Msi

[wasm-tools] Installation Source: VS 17.8.34322.80 Manifest Version: 8.0.5/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.workload.mono.toolchain.current\8.0.5\WorkloadManifest.json Install Type: Msi

[android] Installation Source: VS 17.8.34322.80 Manifest Version: 34.0.43/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.android\34.0.43\WorkloadManifest.json Install Type: Msi

Host: Version: 8.0.5 Architecture: x64 Commit: 087e15321b

.NET SDKs installed: 5.0.404 [C:\Program Files\dotnet\sdk] 6.0.302 [C:\Program Files\dotnet\sdk] 6.0.422 [C:\Program Files\dotnet\sdk] 8.0.100 [C:\Program Files\dotnet\sdk] 8.0.105 [C:\Program Files\dotnet\sdk]

.NET runtimes installed: Microsoft.AspNetCore.All 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.22 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.22 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.1.22 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.30 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]


- The IDE (VS / VS Code/ VS4Mac) you're running on, and its version
   Microsoft Visual Studio Professional 2022 (64-bit) - Current Version 17.8.2
KalleOlaviNiemitalo commented 5 months ago

This belongs in dotnet/runtime, area-Extensions-Configuration.

AFAICT, ConfigurationBinder in v6.0.0 does not recognize ISet\<>. I guess its BindInstance method gets the existing HashSet\<Uri> in the object instance parameter, detects that the instance supports ICollection\<Uri>, and calls BindCollection, which then calls the ICollection\<Uri>.Add(Uri) method repeatedly.

Between v6.0.0 and v8.0.0, there was a change to make ConfigurationBinder recognize ISet\<>: https://github.com/dotnet/runtime/pull/68133. Now in v8.0.0, BindInstance is told that the type of the property is ISet\<Uri>, and presumably calls BindSet, which gives up:

https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs#L799-L803

KalleOlaviNiemitalo commented 5 months ago

Limiting this to strings and enums was requested in https://github.com/dotnet/runtime/pull/68133#discussion_r864098229.

dotnet-policy-service[bot] commented 5 months ago

Tagging subscribers to this area: @dotnet/area-extensions-configuration See info in area-owners.md if you want to be subscribed.

tarekgh commented 5 months ago

CC @halter73 for the comment https://github.com/dotnet/runtime/issues/102926#issuecomment-2142599852

KalleOlaviNiemitalo commented 3 months ago

For keys in an IDictionary\<TKey, TValue>, the string-or-enum restriction makes some sense, because those are converted from JSON property names that can only be strings.

For elements of ISet\<T>, the restriction makes less sense, because they are converted from JSON array elements that can have any JSON type.