feat: Add Dependency Injection extensions for Adding Flagd Provider
This PR
Adds extension methods to OpenFeatureBuilder for adding and configuring the FlagdProvider.
Related Issues
Fixes #376
Notes
If we're going to add these Extension methods we need to bump the OpenFeature dependency up to >2.2.0.
You can inject Flagd with the following Extension methods:
builder.Services.AddOpenFeature(config =>
{
config.AddHostedFeatureLifecycle()
// With default configuration
.AddFlagdProvider()
// Flagd is setup with default configuration and bounded to the "domain1"
.AddFlagdProvider("domain1")
// Flagd is setup with custom configuration
.AddFlagdProvider(new FlagdProviderOptions
{
Host = "localhost",
Port = 8013,
UseTls = true
})
// Flagd is setup with custom configuration and bounded to the "domain1"
.AddFlagdProvider("domain1", new FlagdProviderOptions
{
Host = "localhost"
})
);
});
Follow-up Tasks
I think having some integration test for this with TestContainers would be valuable. I have written one locally but as OpenFeature.Hosting is limited to net8 and net9 I would have to drop net462 as a TFM. I have raised an issue for this in the SDK here: https://github.com/open-feature/dotnet-sdk/issues/472
Updating the README for Flagd package with guide on how to use the Dependency Injection extension methods.
How to test
I prefer the property initialisation approach, especially since we are configuring and not adding any extra service.
Hi all 👋
@kylejuliandev Thanks for the great work on this! I’d like to suggest as enhancement to improve the registration and configuration logic for the FlagdProvider.
Right now, the provider is registered with manual option mapping and provider creation logic. I believe we can improve this by leveraging the .NET Options pattern alongside a dedicated builder extension method (e.g., ToFlagdConfigBuilder). This would align the implementation more closely with idiomatic .NET practices.
Advantages of this approach:
- Testability: Configuration can be easily overridden in unit or integration tests using DI, without hardcoding values. In integration tests, we can override options just by configuring the DI container - no need to poke internals or use reflection hacks.
- Flexibility: Supports configuration from multiple sources (code, config files, environment variables) and enables named options for multi-tenant scenarios.
- Maintainability: Centralizes config mapping in one place (e.g.,
ToFlagdConfigBuilder()), simplifying updates when new fields are introduced. - Cleaner Registration: Keeps the provider registration concise and consistent across overloads.
- Familiar to .NET developers: Follows common patterns, which helps new contributors understand and extend the SDK more easily.
Example (proposed):
public static OpenFeatureBuilder AddFlagdProvider(this OpenFeatureBuilder builder)
{
builder.Services.AddOptions<FlagdProviderOptions>();
return builder.AddProvider(sp => CreateProvider(sp, null));
}
public static OpenFeatureBuilder AddFlagdProvider(this OpenFeatureBuilder builder, Action<FlagdProviderOptions> configureOptions)
{
builder.Services.Configure<FlagdProviderOptions>(configureOptions);
return builder.AddProvider(sp => CreateProvider(sp, null));
}
public static OpenFeatureBuilder AddFlagdProvider(this OpenFeatureBuilder builder, string domain)
{
builder.Services.AddOptions<FlagdProviderOptions>(domain);
return builder.AddProvider(domain, (sp, d) => CreateProvider(sp, d));
}
public static OpenFeatureBuilder AddFlagdProvider(this OpenFeatureBuilder builder, string domain, Action<FlagdProviderOptions> configureOptions)
{
builder.Services.Configure<FlagdProviderOptions>(domain, configureOptions);
return builder.AddProvider(domain, (sp, d) => CreateProvider(sp, d));
}
private static FlagdProvider CreateProvider(IServiceProvider provider, string domain)
{
var logger = provider.GetService<ILogger<FlagdProvider>>() ?? NullLogger<FlagdProvider>.Instance;
var optionsMonitor = provider.GetRequiredService<IOptionsMonitor<FlagdProviderOptions>>();
var options = optionsMonitor.Get(domain ?? Options.DefaultName);
var configBuilder = options.ToFlagdConfigBuilder();
configBuilder.WithLogger(logger);
return new FlagdProvider(configBuilder.Build());
}
Let me know what you think!