diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index a066078f..6ff52bc0 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -196,11 +196,17 @@ public IAsyncEnumerable GetFeatureNamesAsync() /// An enumerator which provides asynchronous iteration over the feature names registered in the feature manager. public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) { + var featureNamesReturned = new HashSet(); await foreach (FeatureDefinition featureDefinition in _featureDefinitionProvider.GetAllFeatureDefinitionsAsync().ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); - yield return featureDefinition.Name; + if (!featureNamesReturned.Contains(featureDefinition.Name)) + { + yield return featureDefinition.Name; + + featureNamesReturned.Add(featureDefinition.Name); + } } } diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs index fe2e2ac5..a9870ffa 100644 --- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs +++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs @@ -394,6 +394,79 @@ public async Task LastFeatureFlagWins() Assert.True(await featureManager.IsEnabledAsync(Features.DuplicateFlag)); } + + [Fact] + public async Task DedupesFeatureNames() + { + IConfiguration configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { $"FeatureManagement:{Features.DuplicateFlag}", "False" } + }) + .AddInMemoryCollection(new Dictionary + { + { $"FeatureManagement:{Features.DuplicateFlag}", "True" } + }) + .Build(); + + IServiceCollection services = new ServiceCollection(); + + services + .AddSingleton(configuration) + .AddFeatureManagement(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + var featureNames = await featureManager.GetFeatureNamesAsync().ToListAsync(); + Assert.Equal(featureNames.Distinct().Count(), featureNames.Count); + Assert.True(await featureManager.IsEnabledAsync(Features.DuplicateFlag)); + } + } + + public class FeatureManagementMultipleSchemasTest + { + [Fact] + public async Task DedupesFeatureNames() + { + IConfiguration configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.MultipleSchemas.json") + .Build(); + + IServiceCollection services = new ServiceCollection(); + + services + .AddSingleton(configuration) + .AddFeatureManagement(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + var featureNames = await featureManager.GetFeatureNamesAsync().ToListAsync(); + Assert.Equal(featureNames.Distinct().Count(), featureNames.Count); + } + + [Fact] + public async Task PrioritizesMicrosoftSchemaConfiguration() + { + IConfiguration configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.MultipleSchemas.json") + .Build(); + + IServiceCollection services = new ServiceCollection(); + + services + .AddSingleton(configuration) + .AddFeatureManagement(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync(Features.DuplicateFlag)); + } } public class FeatureManagementFeatureFilterGeneralTest diff --git a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj index 4a915a4f..a6e09d1d 100644 --- a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj +++ b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj @@ -1,4 +1,4 @@ - + net48;net8.0;net9.0 @@ -43,6 +43,9 @@ Always + + Always + Always diff --git a/tests/Tests.FeatureManagement/appsettings.MultipleSchemas.json b/tests/Tests.FeatureManagement/appsettings.MultipleSchemas.json new file mode 100644 index 00000000..de91299d --- /dev/null +++ b/tests/Tests.FeatureManagement/appsettings.MultipleSchemas.json @@ -0,0 +1,22 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*", + + // MS schema will be prioritized, feature should be enabled + "feature_management": { + "feature_flags": [ + { + "id": "DuplicateFlag", + "enabled": true + } + ] + }, + + "FeatureManagement": { + "DuplicateFlag": false + } +}