Context
I'm implementing hierarchical feature dependencies where one feature can depend on other features being enabled. I'm using a database-backed provider (similar to PR #389) with AddScopedFeatureManagement().
The Scenario
I want to implement a FeatureDependency filter that allows one feature to depend on others:
{
"id": "Dashboard",
"enabled": true,
"conditions": {
"requirement_type": "Any",
"client_filters": [
{
"name": "FeatureDependency",
"parameters": { "FeatureName": "BasicReporting" }
},
{
"name": "FeatureDependency",
"parameters": { "FeatureName": "AdvancedReporting" }
}
]
}
}
The Dashboard feature should be enabled if either BasicReporting OR AdvancedReporting is enabled.
These dependent features themselves have their own conditions:
{
"id": "BasicReporting",
"enabled": true,
"conditions": {
"client_filters": [
{
"name": "TimeWindow",
"parameters": { "Start": "...", "End": "..." }
}
]
}
}
{
"id": "AdvancedReporting",
"enabled": true,
"conditions": {
"requirement_type": "All",
"client_filters": [
{
"name": "Percentage",
"parameters": { "Value": 50 }
},
{
"name": "FeatureDependency",
"parameters": { "FeatureName": "PremiumSubscription" }
}
]
}
}
To properly evaluate Dashboard, the FeatureDependency filter needs to evaluate BasicReporting and AdvancedReporting through their complete filter pipeline (TimeWindow, Percentage, nested dependencies, etc.). This requires calling IFeatureManager.IsEnabledAsync().
What I Tried
I implemented the filter like this:
[FilterAlias("FeatureDependency")]
public class FeatureDependencyFilter : IFeatureFilter
{
private readonly IFeatureManager _featureManager;
public FeatureDependencyFilter(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
{
string? featureName = context.Parameters.GetValue<string>("FeatureName");
if (string.IsNullOrEmpty(featureName))
{
return false;
}
return await _featureManager.IsEnabledAsync(featureName);
}
}
Registered with:
services.AddScopedFeatureManagement()
.AddFeatureFilter<FeatureDependencyFilter>();
The Problem
This creates a circular dependency during DI container construction:
IFeatureManager (being created)
→ needs IFeatureFilterMetadata[] (including FeatureDependencyFilter)
→ FeatureDependencyFilter constructor needs IFeatureManager
→ Circular dependency!
Result: The application hangs indefinitely when trying to resolve IFeatureManager:
using (IServiceScope scope = serviceProvider.CreateScope())
{
IFeatureManager featureManager = scope.ServiceProvider.GetRequiredService<IFeatureManager>();
}
The Solution I Arrived At
Breaking the circular dependency by resolving IFeatureManager at evaluation time instead of in the constructor:
[FilterAlias("FeatureDependency")]
public class FeatureDependencyFilter : IFeatureFilter
{
private readonly IServiceProvider _serviceProvider;
public FeatureDependencyFilter(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
{
string? featureName = context.Parameters.GetValue<string>("FeatureName");
if (string.IsNullOrEmpty(featureName))
{
return false;
}
IFeatureManager featureManager = _serviceProvider.GetRequiredService<IFeatureManager>();
return await featureManager.IsEnabledAsync(featureName);
}
}
Questions
- Is this the recommended pattern for implementing hierarchical feature dependencies?
- Is there a built-in way to express feature dependencies without creating a custom filter?
- Is using
IServiceProvider directly the correct approach to break the circular dependency, or is there a better pattern?
- Does the default configuration-based provider handle cycle detection? If feature dependencies form a cycle (e.g., FeatureA → FeatureB → FeatureA), does the library have built-in safeguards, or should I implement my own cycle detection?
Any guidance on the proper pattern for hierarchical feature dependencies would be greatly appreciated!
Context
I'm implementing hierarchical feature dependencies where one feature can depend on other features being enabled. I'm using a database-backed provider (similar to PR #389) with
AddScopedFeatureManagement().The Scenario
I want to implement a
FeatureDependencyfilter that allows one feature to depend on others:{ "id": "Dashboard", "enabled": true, "conditions": { "requirement_type": "Any", "client_filters": [ { "name": "FeatureDependency", "parameters": { "FeatureName": "BasicReporting" } }, { "name": "FeatureDependency", "parameters": { "FeatureName": "AdvancedReporting" } } ] } }The
Dashboardfeature should be enabled if eitherBasicReportingORAdvancedReportingis enabled.These dependent features themselves have their own conditions:
{ "id": "BasicReporting", "enabled": true, "conditions": { "client_filters": [ { "name": "TimeWindow", "parameters": { "Start": "...", "End": "..." } } ] } }{ "id": "AdvancedReporting", "enabled": true, "conditions": { "requirement_type": "All", "client_filters": [ { "name": "Percentage", "parameters": { "Value": 50 } }, { "name": "FeatureDependency", "parameters": { "FeatureName": "PremiumSubscription" } } ] } }To properly evaluate
Dashboard, theFeatureDependencyfilter needs to evaluateBasicReportingandAdvancedReportingthrough their complete filter pipeline (TimeWindow, Percentage, nested dependencies, etc.). This requires callingIFeatureManager.IsEnabledAsync().What I Tried
I implemented the filter like this:
Registered with:
The Problem
This creates a circular dependency during DI container construction:
Result: The application hangs indefinitely when trying to resolve
IFeatureManager:The Solution I Arrived At
Breaking the circular dependency by resolving
IFeatureManagerat evaluation time instead of in the constructor:Questions
IServiceProviderdirectly the correct approach to break the circular dependency, or is there a better pattern?Any guidance on the proper pattern for hierarchical feature dependencies would be greatly appreciated!