Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ sealed class ConfigurationFeatureDefinitionProvider : IFeatureDefinitionProvider
private const string FeatureManagementSectionName = "FeatureManagement";
private readonly IConfiguration _configuration;
private readonly ConcurrentDictionary<string, FeatureDefinition> _definitions;
private IDisposable _changeSubscription;
private readonly ILogger _logger;
private IDisposable _changeSubscription;
private int _stale = 0;

public ConfigurationFeatureDefinitionProvider(IConfiguration configuration, ILoggerFactory loggerFactory)
Expand All @@ -42,6 +42,11 @@ public ConfigurationFeatureDefinitionProvider(IConfiguration configuration, ILog
() => _stale = 1);
}

/// <summary>
/// The option that controls the behavior when "FeatureManagement" section in the configuration is missing.
/// </summary>
public bool UseTopLevelConfiguration { get; init; }

public void Dispose()
{
_changeSubscription?.Dispose();
Expand Down Expand Up @@ -217,6 +222,13 @@ private IEnumerable<IConfigurationSection> GetFeatureDefinitionSections()
return featureManagementConfigurationSection.GetChildren();
}

//
// There is no "FeatureManagement" section in the configuration
if (UseTopLevelConfiguration)
{
return _configuration.GetChildren();
}

_logger.LogDebug($"No configuration section named '{FeatureManagementSectionName}' was found.");

return Enumerable.Empty<IConfigurationSection>();
Expand Down
18 changes: 16 additions & 2 deletions src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec

/// <summary>
/// Adds singleton <see cref="FeatureManager"/> and other required feature management services.
/// The registered <see cref="ConfigurationFeatureDefinitionProvider"/> will use the provided configuration and read from the top level if no "FeatureManagement" section can be found.
/// </summary>
/// <param name="services">The service collection that feature management services are added to.</param>
/// <param name="configuration">A specific <see cref="IConfiguration"/> instance that will be used to obtain feature settings.</param>
Expand All @@ -80,7 +81,13 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec
throw new ArgumentNullException(nameof(configuration));
}

services.AddSingleton<IFeatureDefinitionProvider>(sp => new ConfigurationFeatureDefinitionProvider(configuration, sp.GetRequiredService<ILoggerFactory>()));
services.AddSingleton<IFeatureDefinitionProvider>(sp =>
new ConfigurationFeatureDefinitionProvider(
configuration,
sp.GetRequiredService<ILoggerFactory>())
{
UseTopLevelConfiguration = true
});

return services.AddFeatureManagement();
}
Expand Down Expand Up @@ -135,6 +142,7 @@ public static IFeatureManagementBuilder AddScopedFeatureManagement(this IService

/// <summary>
/// Adds scoped <see cref="FeatureManager"/> and other required feature management services.
/// The registered <see cref="ConfigurationFeatureDefinitionProvider"/> will use the provided configuration and read from the top level if no "FeatureManagement" section can be found.
/// </summary>
/// <param name="services">The service collection that feature management services are added to.</param>
/// <param name="configuration">A specific <see cref="IConfiguration"/> instance that will be used to obtain feature settings.</param>
Expand All @@ -146,7 +154,13 @@ public static IFeatureManagementBuilder AddScopedFeatureManagement(this IService
throw new ArgumentNullException(nameof(configuration));
}

services.AddSingleton<IFeatureDefinitionProvider>(sp => new ConfigurationFeatureDefinitionProvider(configuration, sp.GetRequiredService<ILoggerFactory>()));
services.AddSingleton<IFeatureDefinitionProvider>(sp =>
new ConfigurationFeatureDefinitionProvider(
configuration,
sp.GetRequiredService<ILoggerFactory>())
{
UseTopLevelConfiguration = true
});

return services.AddScopedFeatureManagement();
}
Expand Down
23 changes: 21 additions & 2 deletions tests/Tests.FeatureManagement/FeatureManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ public async Task ReadsOnlyFeatureManagementSection()

services
.AddSingleton(config)
.AddFeatureManagement()
.AddFeatureFilter<TestFilter>();
.AddFeatureManagement();

ServiceProvider serviceProvider = services.BuildServiceProvider();

Expand All @@ -87,6 +86,26 @@ public async Task ReadsOnlyFeatureManagementSection()
}
}

[Fact]
public async Task ReadsTopLevelConfiguration()
{
const string feature = "FeatureX";

var stream = new MemoryStream(Encoding.UTF8.GetBytes($"{{\"AllowedHosts\": \"*\", \"FeatureFlags\": {{\"{feature}\": true}}}}"));

IConfiguration config = new ConfigurationBuilder().AddJsonStream(stream).Build();

var services = new ServiceCollection();

services.AddFeatureManagement(config.GetSection("FeatureFlags"));

ServiceProvider serviceProvider = services.BuildServiceProvider();

IFeatureManager featureManager = serviceProvider.GetRequiredService<IFeatureManager>();

Assert.True(await featureManager.IsEnabledAsync(feature));
}

[Fact]
public void AddsScopedFeatureManagement()
{
Expand Down