Skip to content

[.NET 6] Binding to non-null IReadOnlyDictionary doesn't work #105960

Open
@eduherminio

Description

@eduherminio

Description

Potentially related/similar: #36390

Using .NET 6 and Microsoft.Extensions.Configuration v6 to bind key-value pairs to an IReadOnlyDictionary doesn't work if the property is initialized (to an empty Dictionary).

It works when using .NET 8 and Microsoft.Extensions.Configuration v8, but I'm not sure whether there's some fix that needs to be done internally so that this issue doesn't show up in some other scenario.

I also want to validate that the suggested workaround is the best way to move forward.

Reproduction Steps

Repro in Bind-Not-Null-IReadOnlyDictionary repo

git clone https://github.com/eduherminio/Bind-Not-Null-IReadOnlyDictionary IReadOnlyDictionary 
cd IReadOnlyDictionary
dotnet run --framework net8.0
dotnet run --framework net6.0

, opt => opt.ErrorOnUnknownConfiguration = true can be uncommented to see the exception described below.

Expected behavior

Binding to an initialized IReadOnlyDictionary works

{
  "Options": {
    "Enabled": true,

    "Fields": {
      "Name": "",
      "Id": "1"
    }
  }
}
public class MyOptions
{
    public bool Enabled { get; set; } = false;
    public IReadOnlyDictionary<string, object> Fields { get; set; } = new Dictionary<string, object>();
}

Actual behavior

Binding to an initialized IReadOnlyDictionary doesn't work and it shows up as empty

With ErrorOnUnknownConfiguration = true the following exception shows up:

Unhandled exception. System.InvalidOperationException: 'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of System.Collections.Generic.Dictionary`2[System.String,System.Object]: 'Id', 'Name'
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindNonScalar(IConfiguration configuration, Object instance, BinderOptions options)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindInstance(Type type, Object instance, IConfiguration config, BinderOptions options)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.GetPropertyValue(PropertyInfo property, Object instance, IConfiguration config, BinderOptions options)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindProperty(PropertyInfo property, Object instance, IConfiguration config, BinderOptions options)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindNonScalar(IConfiguration configuration, Object instance, BinderOptions options)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindInstance(Type type, Object instance, IConfiguration config, BinderOptions options)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.Bind(IConfiguration configuration, Object instance, Action`1 configureOptions)
   at Program.<Main>$(String[] args) in C:\dev\dotnet\IReadOnlyDictionaryIssue\Program.cs:line 10

Regression?

No response

Known Workarounds

Our use case is a library targeting .NET 6 and .NET 8, so not initializing the IReadOnlyDictionary is one of the workarounds

public IReadOnlyDictionary<string, object> Fields { get; set; }
    //#if NET8_0_OR_GREATER
    //    = new Dictionary<string, object>();
    //#else
    //    = null!;
    //#endif

Another one would be to avoid using IReadOnlyDictionary, but in our case we can't change our API surface like that.

Can you think of any other workaround that doesn't imply pinky promising that the property will be initialized somewhere?

Configuration

.NET SDK 6.0.424, .NET runtime Microsoft.NETCore.App 6.0.32

    <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-Extensions-Configurationneeds-further-triageIssue has been initially triaged, but needs deeper consideration or reconsiderationquestionAnswer questions and provide assistance, not an issue with source code or documentation.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions