Skip to content
Merged
Prev Previous commit
Next Next commit
merge feature flags from configuration providers
  • Loading branch information
zhiyuanliang-ms committed May 15, 2025
commit bedd802431f94a28038e252923cae0204dbb2324
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -22,8 +23,10 @@ public sealed class ConfigurationFeatureDefinitionProvider : IFeatureDefinitionP
//
// IFeatureDefinitionProviderCacheable interface is only used to mark this provider as cacheable. This allows our test suite's
// provider to be marked for caching as well.

private static readonly PropertyInfo _propertyInfo = typeof(ConfigurationProvider).GetProperty("Data", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put a comment that compile time dependency ensures presence of this property via OnDemandConfigurationProvider.Data

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish we could really take advantage of the compile time dependency. Like if we could have a static method inside of OnDemandConfigurationProvider that uses nameof(data) to enforce the dependency.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal class OnDemandConfigurationProvider : ConfigurationProvider
{
    private static readonly PropertyInfo _propertyInfo = typeof(ConfigurationProvider).GetProperty(nameof(Data), BindingFlags.NonPublic | BindingFlags.Instance);

    public OnDemandConfigurationProvider(ConfigurationProvider configurationProvider)
    {
        var data = _propertyInfo.GetValue(configurationProvider) as IDictionary<string, string>;

        Data = data;
    }
}

Is this what you want? @jimmyca15

private readonly IConfiguration _configuration;
private IEnumerable<IConfigurationSection> _dotnetFeatureDefinitionSections;
private IEnumerable<IConfigurationSection> _microsoftFeatureDefinitionSections;
private readonly ConcurrentDictionary<string, Task<FeatureDefinition>> _definitions;
private IDisposable _changeSubscription;
private int _stale = 0;
Expand All @@ -48,6 +51,10 @@ public ConfigurationFeatureDefinitionProvider(IConfiguration configuration)
{
return Task.FromResult(GetMicrosoftSchemaFeatureDefinition(featureName) ?? GetDotnetSchemaFeatureDefinition(featureName));
};

_dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections();

_microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections();
}

/// <summary>
Expand Down Expand Up @@ -90,6 +97,10 @@ public Task<FeatureDefinition> GetFeatureDefinitionAsync(string featureName)
if (Interlocked.Exchange(ref _stale, 0) != 0)
{
_definitions.Clear();

_dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections();

_microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections();
}

return _definitions.GetOrAdd(featureName, _getFeatureDefinitionFunc);
Expand All @@ -109,11 +120,13 @@ public async IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync()
if (Interlocked.Exchange(ref _stale, 0) != 0)
{
_definitions.Clear();
}

IEnumerable<IConfigurationSection> microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections();
_dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections();

_microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections();
}

foreach (IConfigurationSection featureSection in microsoftFeatureDefinitionSections)
foreach (IConfigurationSection featureSection in _microsoftFeatureDefinitionSections)
{
string featureName = featureSection[MicrosoftFeatureManagementFields.Id];

Expand All @@ -132,9 +145,7 @@ public async IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync()
}
}

IEnumerable<IConfigurationSection> dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections();

foreach (IConfigurationSection featureSection in dotnetFeatureDefinitionSections)
foreach (IConfigurationSection featureSection in _dotnetFeatureDefinitionSections)
{
string featureName = featureSection.Key;

Expand All @@ -156,9 +167,7 @@ public async IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync()

private FeatureDefinition GetDotnetSchemaFeatureDefinition(string featureName)
{
IEnumerable<IConfigurationSection> dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections();

IConfigurationSection dotnetFeatureDefinitionConfiguration = dotnetFeatureDefinitionSections
IConfigurationSection dotnetFeatureDefinitionConfiguration = _dotnetFeatureDefinitionSections
.FirstOrDefault(section =>
string.Equals(section.Key, featureName, StringComparison.OrdinalIgnoreCase));

Expand All @@ -172,9 +181,7 @@ private FeatureDefinition GetDotnetSchemaFeatureDefinition(string featureName)

private FeatureDefinition GetMicrosoftSchemaFeatureDefinition(string featureName)
{
IEnumerable<IConfigurationSection> microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections();

IConfigurationSection microsoftFeatureDefinitionConfiguration = microsoftFeatureDefinitionSections
IConfigurationSection microsoftFeatureDefinitionConfiguration = _microsoftFeatureDefinitionSections
.LastOrDefault(section =>
string.Equals(section[MicrosoftFeatureManagementFields.Id], featureName, StringComparison.OrdinalIgnoreCase));

Expand Down Expand Up @@ -211,9 +218,48 @@ private IEnumerable<IConfigurationSection> GetDotnetFeatureDefinitionSections()

private IEnumerable<IConfigurationSection> GetMicrosoftFeatureDefinitionSections()
{
return _configuration.GetSection(MicrosoftFeatureManagementFields.FeatureManagementSectionName)
.GetSection(MicrosoftFeatureManagementFields.FeatureFlagsSectionName)
.GetChildren();
var configurationRoot = _configuration as IConfigurationRoot;
if (configurationRoot == null)
{
// Fallback to current behavior if not an IConfigurationRoot
return _configuration
.GetSection(MicrosoftFeatureManagementFields.FeatureManagementSectionName)
.GetSection(MicrosoftFeatureManagementFields.FeatureFlagsSectionName)
.GetChildren();
}

var mergedSections = new List<IConfigurationSection>();

foreach (IConfigurationProvider provider in configurationRoot.Providers)
{
if (provider is ConfigurationProvider configProvider)
{
var data = _propertyInfo.GetValue(configProvider) as IDictionary<string, string>;

//
// Cannot use the original provider directly as its reload token is subscribed
var configurationProvider = new OnDemandConfigurationProvider(data);

var onDemandConfigurationRoot = new ConfigurationRoot(new[] { configurationProvider });

IConfigurationSection featureFlagsSection = onDemandConfigurationRoot
.GetSection(MicrosoftFeatureManagementFields.FeatureManagementSectionName)
.GetSection(MicrosoftFeatureManagementFields.FeatureFlagsSectionName);

if (featureFlagsSection.Exists())
{
mergedSections.AddRange(featureFlagsSection.GetChildren());
}
}
else if (provider is ChainedConfigurationProvider chainedProvider)
{
IConfigurationSection featureFlagsSection = chainedProvider.Configuration
.GetSection(MicrosoftFeatureManagementFields.FeatureManagementSectionName)
.GetSection(MicrosoftFeatureManagementFields.FeatureFlagsSectionName);
}
}

return mergedSections;
}

private FeatureDefinition ParseDotnetSchemaFeatureDefinition(IConfigurationSection configurationSection)
Expand Down
13 changes: 13 additions & 0 deletions src/Microsoft.FeatureManagement/OnDemandConfigurationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;

namespace Microsoft.FeatureManagement
{
internal class OnDemandConfigurationProvider : ConfigurationProvider
{
public OnDemandConfigurationProvider(IDictionary<string, string> data)
{
Data = data;
}
}
}