Skip to content

ServiceCollection.Configure<ConcurrentDictionary<string, T>>(IConfigurationSection) fails in NET7 #78994

Closed

Description

@matthewDDennis

Description

I have an ModuleCollection class which is

   /// <summary>
    /// The set of modules for backend processing.
    /// </summary>
    public class ModuleCollection : ConcurrentDictionary<string, ModuleConfig>
    {
        /// <summary>
        /// This constructor allows our modules collection to be case insensitive on the key.
        /// </summary>
        public ModuleCollection() : base(StringComparer.OrdinalIgnoreCase) { }
    }

In NET6

            services.Configure<ModuleCollection>(configuration.GetSection("Modules"));

would load the ModuleCollection with the ModuleConfigs from multiple modulesettings.json files that had been registered with the ConfigurationBuilder. TheIOption<ModuleCollection>injected would have the populated collection as its value.

In NET7 the IOption<ModuleCollection>.Value is an empty ModuleCollection

I worked around this by

            services.AddOptions<ModuleCollection>()
                    .Configure(moduleCollection =>
                    {
                        var moduleNames = configuration.GetSection("Modules").GetChildren().Select(x => x.Key).ToList();
                        foreach (var moduleName in moduleNames)
                        {
                            if (moduleName is not null)
                            {
                                ModuleConfig moduleConfig = new ModuleConfig();
                                configuration.Bind($"Modules:{moduleName}", moduleConfig);
                                moduleCollection.TryAdd(moduleName, moduleConfig);
                            }
                        }
                    });

Reproduction Steps

see above

Expected behavior

Same as NET6
IOption<ModuleCollection>.Value contains the collection of ModuleConfigs/

Actual behavior

The IOption<ModuleCollection>.Value is an empty collection.

Regression?

It worked on NET6

Known Workarounds

            ModuleCollection moduleCollection = new();
            services.AddOptions<ModuleCollection>()
                    .Configure(moduleCollection =>
                    {
                        var moduleNames = configuration.GetSection("Modules").GetChildren().Select(x => x.Key).ToList();
                        foreach (var moduleName in moduleNames)
                        {
                            if (moduleName is not null)
                            {
                                ModuleConfig moduleConfig = new ModuleConfig();
                                configuration.Bind($"Modules:{moduleName}", moduleConfig);
                                moduleCollection.TryAdd(moduleName, moduleConfig);
                            }
                        }
                    });

Configuration

NET7, VS2022 17.4.1

Other information

I can bind to a single ModuleConfig, just the binding to the dictionary is failing, so it is probably in the Dictionary Binder in NET7.

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Nov 29, 2022
  2. matthewDDennis commented on Nov 29, 2022

    @matthewDDennis
    Author

    Probably should be labeled area-Extensions-Options

  3. changed the title [-]ServiceCollection.Configure<Dictionary<string, T>>(IConfigurationSection) fails in NET7[/-] [+]ServiceCollection.Configure<ConcurrentDictionary<string, T>>(IConfigurationSection) fails in NET7[/+] on Nov 29, 2022
  4. RichardD2 commented on Nov 30, 2022

    @RichardD2

    Do you actually need the ModuleCollection moduleCollection = new(); line? That variable doesn't seem to be used anywhere else - the other references use the lambda method parameter with the same name.

  5. matthewDDennis commented on Nov 30, 2022

    @matthewDDennis
    Author

    Do you actually need the ModuleCollection moduleCollection = new(); line? That variable doesn't seem to be used anywhere else - the other references use the lambda method parameter with the same name.

    You are correct. That was a copy & paste error from my test code to determine what was broken.
    I've updated my question.

    Thanks.

  6. tarekgh commented on Nov 30, 2022

    @tarekgh
    Member

    @matthewDDennis could you please try to run using Microsoft.Extensions.* packages from the nuget private feed

        <add key="dotnet8" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json" />

    and use the version: 8.0.0-alpha.1.22578.1

    I am asking because this look it is already fixed by #78118.

    If it is still reproduced, please provide a small app that reproduces the problem we can try. Thanks.

  7. added this to the Future milestone on Nov 30, 2022
  8. ghost removed
    untriagedNew issue has not been triaged by the area owner
    on Nov 30, 2022
  9. added
    needs-author-actionAn issue or pull request that requires more info or actions from the author.
    on Nov 30, 2022
  10. matthewDDennis commented on Nov 30, 2022

    @matthewDDennis
    Author

    I am not able to do that at the moment as I am working on the migration from net6 to net7.
    I still have some work to go before I will be able to branch and check that out.
    I see you have back ported the fix to 7.0.1.
    Is that available in the standard NuGet feed yet?

    However, the description on #78118 looks exactly like what I am seeing.
    Once I get to a good point I will check it out, preferably with 7.0.1.

  11. ghost added
    needs-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration
    on Nov 30, 2022
  12. ghost removed
    needs-author-actionAn issue or pull request that requires more info or actions from the author.
    on Nov 30, 2022
  13. removed
    needs-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration
    on Dec 1, 2022
  14. tarekgh commented on Dec 1, 2022

    @tarekgh
    Member

    Either you can wait for .NET 7.0.1 release, or you try what I mentioned in the comment #78994 (comment) or download the file https://dotnetbuilds.azureedge.net/public/aspnetcore/Runtime/7.0.1-servicing.22565.7/aspnetcore-runtime-7.0.1-win-x64.zip and copy the assembly Microsoft.Extensions.Configuration.Binder.dll to your dotnet\shared\Microsoft.AspNetCore.App\7.0.0 folder. Make sure to back up the original file before overwriting it so you may copy it back.

  15. added
    needs-author-actionAn issue or pull request that requires more info or actions from the author.
    on Dec 1, 2022
  16. ghost closed this as completedon Dec 29, 2022
  17. self-assigned this
    on Dec 29, 2022
  18. tarekgh commented on Jan 5, 2023

    @tarekgh
    Member

    I have confirmed this issue is fixed in 7.0.1. I used the following code. I can see the issue repro on 7.0.0.

    repro code

            public class ModuleCollection : ConcurrentDictionary<string, string>
            {
                public ModuleCollection() : base(StringComparer.OrdinalIgnoreCase) { }
            }
    
    
            var configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();
    
            var services = new ServiceCollection();
            services.Configure<ModuleCollection>(configuration.GetSection("Modules"));
    
            var val = services.BuildServiceProvider().GetRequiredService<IOptions<ModuleCollection>>().Value;

    appsettings.json

    {
        "Modules": {
            "module1": "moduleConfig1",
            "module2": "moduleConfig2",
            "module3": "moduleConfig3"
        }
    }
  19. ghost locked as resolved and limited conversation to collaborators on Feb 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions