diff --git a/OpenFeature.slnx b/OpenFeature.slnx
index 936079f40..a9e90dcab 100644
--- a/OpenFeature.slnx
+++ b/OpenFeature.slnx
@@ -48,6 +48,7 @@
+
diff --git a/src/OpenFeature.Hosting/Diagnostics/FeatureCodes.cs b/src/OpenFeature.DependencyInjection.Abstractions/Diagnostics/FeatureCodes.cs
similarity index 93%
rename from src/OpenFeature.Hosting/Diagnostics/FeatureCodes.cs
rename to src/OpenFeature.DependencyInjection.Abstractions/Diagnostics/FeatureCodes.cs
index f7ecf81cb..e24b83c66 100644
--- a/src/OpenFeature.Hosting/Diagnostics/FeatureCodes.cs
+++ b/src/OpenFeature.DependencyInjection.Abstractions/Diagnostics/FeatureCodes.cs
@@ -1,4 +1,4 @@
-namespace OpenFeature.Hosting.Diagnostics;
+namespace OpenFeature.DependencyInjection.Abstractions.Diagnostics;
///
/// Contains identifiers for experimental features and diagnostics in the OpenFeature framework.
@@ -22,7 +22,7 @@ namespace OpenFeature.Hosting.Diagnostics;
/// - "001" - Unique identifier for a specific feature.
///
///
-internal static class FeatureCodes
+public static class FeatureCodes
{
///
/// Identifier for the experimental Dependency Injection features within the OpenFeature framework.
diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj
new file mode 100644
index 000000000..be60d49d9
--- /dev/null
+++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netstandard2.0;net8.0;net9.0;net462
+ OpenFeature.DependencyInjection.Abstractions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilder.cs b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilder.cs
new file mode 100644
index 000000000..5ff17f47d
--- /dev/null
+++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilder.cs
@@ -0,0 +1,62 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace OpenFeature.DependencyInjection.Abstractions;
+
+///
+/// Describes a backed by an .
+///
+public abstract class OpenFeatureProviderBuilder(IServiceCollection services)
+{
+ /// The services being configured.
+ public IServiceCollection Services { get; } = services;
+
+ ///
+ /// Gets a value indicating whether a default provider has been registered.
+ ///
+ public bool HasDefaultProvider { get; internal set; }
+
+ ///
+ /// Gets the count of domain-bound providers that have been registered.
+ /// This count does not include the default provider.
+ ///
+ public int DomainBoundProviderRegistrationCount { get; internal set; }
+
+ ///
+ /// Indicates whether the policy has been configured.
+ ///
+ public bool IsPolicyConfigured { get; internal set; }
+
+ ///
+ /// Validates the current configuration, ensuring that a policy is set when multiple providers are registered
+ /// or when a default provider is registered alongside another provider.
+ ///
+ ///
+ /// Thrown if multiple providers are registered without a policy, or if both a default provider
+ /// and an additional provider are registered without a policy configuration.
+ ///
+ public void Validate()
+ {
+ if (IsPolicyConfigured)
+ {
+ return;
+ }
+
+ if (DomainBoundProviderRegistrationCount > 1)
+ {
+ throw new InvalidOperationException("Multiple providers have been registered, but no policy has been configured.");
+ }
+
+ if (HasDefaultProvider && DomainBoundProviderRegistrationCount == 1)
+ {
+ throw new InvalidOperationException("A default provider and an additional provider have been registered without a policy configuration.");
+ }
+ }
+
+ ///
+ /// Adds an IFeatureClient to the container. If is supplied,
+ /// registers a domain-bound client; otherwise registers a global client. If an evaluation context is
+ /// configured, it is applied at resolve-time.
+ ///
+ /// The current .
+ internal protected abstract OpenFeatureProviderBuilder TryAddClient(string? name = null);
+}
diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs
new file mode 100644
index 000000000..02e4dc86b
--- /dev/null
+++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs
@@ -0,0 +1,145 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using OpenFeature.DependencyInjection.Abstractions;
+
+namespace OpenFeature;
+
+///
+/// Contains extension methods for the class.
+///
+#if NET8_0_OR_GREATER
+[System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Abstractions.Diagnostics.FeatureCodes.NewDi)]
+#endif
+public static partial class OpenFeatureProviderBuilderExtensions
+{
+ ///
+ /// Adds a feature provider using a factory method without additional configuration options.
+ /// This method adds the feature provider as a transient service and sets it as the default provider within the application.
+ ///
+ /// The used to configure feature flags.
+ ///
+ /// A factory method that creates and returns a
+ /// instance based on the provided service provider.
+ ///
+ /// The updated instance with the default feature provider set and configured.
+ /// Thrown if the is null, as a valid builder is required to add and configure providers.
+ public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, Func implementationFactory)
+ => AddProvider(builder, implementationFactory, null);
+
+ ///
+ /// Adds a feature provider using a factory method to create the provider instance and optionally configures its settings.
+ /// This method adds the feature provider as a transient service and sets it as the default provider within the application.
+ ///
+ /// Type derived from used to configure the feature provider.
+ /// The used to configure feature flags.
+ ///
+ /// A factory method that creates and returns a
+ /// instance based on the provided service provider.
+ ///
+ /// An optional delegate to configure the provider-specific options.
+ /// The updated instance with the default feature provider set and configured.
+ /// Thrown if the is null, as a valid builder is required to add and configure providers.
+ public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, Func implementationFactory, Action? configureOptions)
+ where TOptions : OpenFeatureProviderOptions
+ {
+ if (builder == null) throw new ArgumentNullException(nameof(builder));
+
+ builder.HasDefaultProvider = true;
+ builder.Services.PostConfigure(options => options.AddDefaultProviderName());
+ if (configureOptions != null)
+ {
+ builder.Services.Configure(configureOptions);
+ }
+
+ builder.Services.TryAddTransient(implementationFactory);
+ builder.TryAddClient();
+ return builder;
+ }
+
+ ///
+ /// Adds a feature provider for a specific domain using provided options and a configuration builder.
+ ///
+ /// Type derived from used to configure the feature provider.
+ /// The used to configure feature flags.
+ /// The unique name of the provider.
+ ///
+ /// A factory method that creates a feature provider instance.
+ /// It adds the provider as a transient service unless it is already added.
+ ///
+ /// An optional delegate to configure the provider-specific options.
+ /// The updated instance with the new feature provider configured.
+ ///
+ /// Thrown if either or is null or if the is empty.
+ ///
+ public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, string domain, Func implementationFactory, Action? configureOptions)
+ where TOptions : OpenFeatureProviderOptions
+ {
+ if (builder == null) throw new ArgumentNullException(nameof(builder));
+
+ builder.DomainBoundProviderRegistrationCount++;
+
+ builder.Services.PostConfigure(options => options.AddProviderName(domain));
+ if (configureOptions != null)
+ {
+ builder.Services.Configure(domain, configureOptions);
+ }
+
+ builder.Services.TryAddKeyedTransient(domain, (provider, key) =>
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+ return implementationFactory(provider, key.ToString()!);
+ });
+
+ builder.TryAddClient(domain);
+ return builder;
+ }
+
+ ///
+ /// Adds a feature provider for a specified domain using the default options.
+ /// This method configures a feature provider without custom options, delegating to the more generic AddProvider method.
+ ///
+ /// The used to configure feature flags.
+ /// The unique name of the provider.
+ ///
+ /// A factory method that creates a feature provider instance.
+ /// It adds the provider as a transient service unless it is already added.
+ ///
+ /// The updated instance with the new feature provider configured.
+ ///
+ /// Thrown if either or is null or if the is empty.
+ ///
+ public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, string domain, Func implementationFactory)
+ => AddProvider(builder, domain, implementationFactory, configureOptions: null);
+
+ ///
+ /// Configures policy name options for OpenFeature using the specified options type.
+ ///
+ /// The type of options used to configure .
+ /// The instance.
+ /// A delegate to configure .
+ /// The configured instance.
+ /// Thrown when the or is null.
+ public static OpenFeatureProviderBuilder AddPolicyName(this OpenFeatureProviderBuilder builder, Action configureOptions)
+ where TOptions : PolicyNameOptions
+ {
+ if (builder == null) throw new ArgumentNullException(nameof(builder));
+ if (configureOptions == null) throw new ArgumentNullException(nameof(configureOptions));
+
+ builder.IsPolicyConfigured = true;
+
+ builder.Services.Configure(configureOptions);
+ return builder;
+ }
+
+ ///
+ /// Configures the default policy name options for OpenFeature.
+ ///
+ /// The instance.
+ /// A delegate to configure .
+ /// The configured instance.
+ public static OpenFeatureProviderBuilder AddPolicyName(this OpenFeatureProviderBuilder builder, Action configureOptions)
+ => AddPolicyName(builder, configureOptions);
+}
diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderOptions.cs b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderOptions.cs
new file mode 100644
index 000000000..218204dfe
--- /dev/null
+++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderOptions.cs
@@ -0,0 +1,61 @@
+using System.Collections.ObjectModel;
+
+namespace OpenFeature.DependencyInjection.Abstractions;
+
+///
+/// Provider-focused options for configuring OpenFeature integrations.
+/// Contains only contracts and metadata that integrations may need.
+///
+public class OpenFeatureProviderOptions
+{
+ private readonly HashSet _providerNames = [];
+
+ ///
+ /// Determines if a default provider has been registered.
+ ///
+ public bool HasDefaultProvider { get; private set; }
+
+ ///
+ /// The of the configured feature provider, if any.
+ /// Typically set by higher-level configuration.
+ ///
+ public Type FeatureProviderType { get; protected internal set; } = null!;
+
+ ///
+ /// Gets a read-only list of registered provider names.
+ ///
+ public IReadOnlyCollection ProviderNames
+ {
+ get
+ {
+ lock (_providerNames)
+ {
+ return new ReadOnlyCollection([.. _providerNames]);
+ }
+ }
+ }
+
+ ///
+ /// Registers the default provider name if no specific name is provided.
+ /// Sets to true.
+ ///
+ internal void AddDefaultProviderName() => AddProviderName(null);
+
+ ///
+ /// Registers a new feature provider name. This operation is thread-safe.
+ ///
+ /// The name of the feature provider to register. Registers as default if null.
+ internal void AddProviderName(string? name)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ HasDefaultProvider = true;
+ return;
+ }
+
+ lock (_providerNames)
+ {
+ _providerNames.Add(name!);
+ }
+ }
+}
diff --git a/src/OpenFeature.Hosting/PolicyNameOptions.cs b/src/OpenFeature.DependencyInjection.Abstractions/PolicyNameOptions.cs
similarity index 84%
rename from src/OpenFeature.Hosting/PolicyNameOptions.cs
rename to src/OpenFeature.DependencyInjection.Abstractions/PolicyNameOptions.cs
index 3dfa76f89..8ea167e43 100644
--- a/src/OpenFeature.Hosting/PolicyNameOptions.cs
+++ b/src/OpenFeature.DependencyInjection.Abstractions/PolicyNameOptions.cs
@@ -1,4 +1,4 @@
-namespace OpenFeature.Hosting;
+namespace OpenFeature.DependencyInjection.Abstractions;
///
/// Options to configure the default feature client name.
diff --git a/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs b/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs
index 4d915946b..332bc5ad4 100644
--- a/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs
+++ b/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using OpenFeature.DependencyInjection.Abstractions;
namespace OpenFeature.Hosting.Internal;
@@ -22,7 +23,14 @@ public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToke
{
this.LogStartingInitializationOfFeatureProvider();
- var options = _serviceProvider.GetRequiredService>().Value;
+ await InitializeProvidersAsync(cancellationToken).ConfigureAwait(false);
+ InitializeHooks();
+ InitializeHandlers();
+ }
+
+ private async Task InitializeProvidersAsync(CancellationToken cancellationToken)
+ {
+ var options = _serviceProvider.GetRequiredService>().Value;
if (options.HasDefaultProvider)
{
var featureProvider = _serviceProvider.GetRequiredService();
@@ -34,7 +42,11 @@ public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToke
var featureProvider = _serviceProvider.GetRequiredKeyedService(name);
await _featureApi.SetProviderAsync(name, featureProvider).ConfigureAwait(false);
}
+ }
+ private void InitializeHooks()
+ {
+ var options = _serviceProvider.GetRequiredService>().Value;
var hooks = new List();
foreach (var hookName in options.HookNames)
{
@@ -43,7 +55,10 @@ public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToke
}
_featureApi.AddHooks(hooks);
+ }
+ private void InitializeHandlers()
+ {
var handlers = _serviceProvider.GetServices();
foreach (var handler in handlers)
{
diff --git a/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj b/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj
index 84e5efa61..81f6d87f1 100644
--- a/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj
+++ b/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/OpenFeature.Hosting/OpenFeatureBuilder.cs b/src/OpenFeature.Hosting/OpenFeatureBuilder.cs
index 177a9fac3..aeaee6073 100644
--- a/src/OpenFeature.Hosting/OpenFeatureBuilder.cs
+++ b/src/OpenFeature.Hosting/OpenFeatureBuilder.cs
@@ -1,4 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using OpenFeature.DependencyInjection.Abstractions;
+using OpenFeature.Model;
namespace OpenFeature.Hosting;
@@ -6,11 +9,8 @@ namespace OpenFeature.Hosting;
/// Describes a backed by an .
///
/// The services being configured.
-public class OpenFeatureBuilder(IServiceCollection services)
+public class OpenFeatureBuilder(IServiceCollection services) : OpenFeatureProviderBuilder(services)
{
- /// The services being configured.
- public IServiceCollection Services { get; } = services;
-
///
/// Indicates whether the evaluation context has been configured.
/// This property is used to determine if specific configurations or services
@@ -19,42 +19,76 @@ public class OpenFeatureBuilder(IServiceCollection services)
public bool IsContextConfigured { get; internal set; }
///
- /// Indicates whether the policy has been configured.
- ///
- public bool IsPolicyConfigured { get; internal set; }
-
- ///
- /// Gets a value indicating whether a default provider has been registered.
+ /// Internal convenience API to add a client by name (or the default client when is null/empty).
+ /// Delegates to the overridable .
///
- public bool HasDefaultProvider { get; internal set; }
+ /// Optional key for a named client registration.
+ /// The current .
+ internal OpenFeatureProviderBuilder AddClient(string? name = null)
+ => TryAddClient(name);
///
- /// Gets the count of domain-bound providers that have been registered.
- /// This count does not include the default provider.
+ /// Adds an to the container, optionally keyed by .
+ /// If an evaluation context is configured, the client is created with that context.
///
- public int DomainBoundProviderRegistrationCount { get; internal set; }
+ /// Optional key for a named client registration.
+ /// The current .
+ protected override OpenFeatureProviderBuilder TryAddClient(string? name = null)
+ => string.IsNullOrWhiteSpace(name) ? AddClient() : AddDomainBoundClient(name!);
///
- /// Validates the current configuration, ensuring that a policy is set when multiple providers are registered
- /// or when a default provider is registered alongside another provider.
+ /// Adds a global (domain-agnostic) .
+ /// The evaluation context (if configured) is resolved per scope at resolve-time.
///
- ///
- /// Thrown if multiple providers are registered without a policy, or if both a default provider
- /// and an additional provider are registered without a policy configuration.
- ///
- public void Validate()
+ /// The current .
+ private OpenFeatureProviderBuilder AddClient()
{
- if (!IsPolicyConfigured)
+ if (IsContextConfigured)
{
- if (DomainBoundProviderRegistrationCount > 1)
+ Services.TryAddScoped(static provider =>
{
- throw new InvalidOperationException("Multiple providers have been registered, but no policy has been configured.");
- }
+ var api = provider.GetRequiredService();
+ var client = api.GetClient();
+ var context = provider.GetRequiredService();
+ client.SetContext(context);
+ return client;
+ });
+ }
+ else
+ {
+ Services.TryAddScoped(static provider =>
+ {
+ var api = provider.GetRequiredService();
+ return api.GetClient();
+ });
+ }
- if (HasDefaultProvider && DomainBoundProviderRegistrationCount == 1)
+ return this;
+ }
+
+ ///
+ private OpenFeatureProviderBuilder AddDomainBoundClient(string name)
+ {
+ if (IsContextConfigured)
+ {
+ Services.TryAddKeyedScoped(name, static (provider, key) =>
{
- throw new InvalidOperationException("A default provider and an additional provider have been registered without a policy configuration.");
- }
+ var api = provider.GetRequiredService();
+ var client = api.GetClient(key!.ToString());
+ var context = provider.GetRequiredService();
+ client.SetContext(context);
+ return client;
+ });
}
+ else
+ {
+ Services.TryAddKeyedScoped(name, static (provider, key) =>
+ {
+ var api = provider.GetRequiredService();
+ return api.GetClient(key!.ToString());
+ });
+ }
+
+ return this;
}
}
diff --git a/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs b/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs
index 52c66c42e..6714ecf61 100644
--- a/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs
+++ b/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs
@@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using OpenFeature.Constant;
+using OpenFeature.DependencyInjection.Abstractions;
using OpenFeature.Hosting;
using OpenFeature.Hosting.Internal;
using OpenFeature.Model;
@@ -51,164 +52,6 @@ public static OpenFeatureBuilder AddContext(this OpenFeatureBuilder builder, Act
return builder;
}
- ///
- /// Adds a feature provider using a factory method without additional configuration options.
- /// This method adds the feature provider as a transient service and sets it as the default provider within the application.
- ///
- /// The used to configure feature flags.
- ///
- /// A factory method that creates and returns a
- /// instance based on the provided service provider.
- ///
- /// The updated instance with the default feature provider set and configured.
- /// Thrown if the is null, as a valid builder is required to add and configure providers.
- public static OpenFeatureBuilder AddProvider(this OpenFeatureBuilder builder, Func implementationFactory)
- => AddProvider(builder, implementationFactory, null);
-
- ///
- /// Adds a feature provider using a factory method to create the provider instance and optionally configures its settings.
- /// This method adds the feature provider as a transient service and sets it as the default provider within the application.
- ///
- /// Type derived from used to configure the feature provider.
- /// The used to configure feature flags.
- ///
- /// A factory method that creates and returns a
- /// instance based on the provided service provider.
- ///
- /// An optional delegate to configure the provider-specific options.
- /// The updated instance with the default feature provider set and configured.
- /// Thrown if the is null, as a valid builder is required to add and configure providers.
- public static OpenFeatureBuilder AddProvider(this OpenFeatureBuilder builder, Func implementationFactory, Action? configureOptions)
- where TOptions : OpenFeatureOptions
- {
- Guard.ThrowIfNull(builder);
-
- builder.HasDefaultProvider = true;
- builder.Services.PostConfigure(options => options.AddDefaultProviderName());
- if (configureOptions != null)
- {
- builder.Services.Configure(configureOptions);
- }
-
- builder.Services.TryAddTransient(implementationFactory);
- builder.AddClient();
- return builder;
- }
-
- ///
- /// Adds a feature provider for a specific domain using provided options and a configuration builder.
- ///
- /// Type derived from used to configure the feature provider.
- /// The used to configure feature flags.
- /// The unique name of the provider.
- ///
- /// A factory method that creates a feature provider instance.
- /// It adds the provider as a transient service unless it is already added.
- ///
- /// An optional delegate to configure the provider-specific options.
- /// The updated instance with the new feature provider configured.
- ///
- /// Thrown if either or is null or if the is empty.
- ///
- public static OpenFeatureBuilder AddProvider(this OpenFeatureBuilder builder, string domain, Func implementationFactory, Action? configureOptions)
- where TOptions : OpenFeatureOptions
- {
- Guard.ThrowIfNull(builder);
-
- builder.DomainBoundProviderRegistrationCount++;
-
- builder.Services.PostConfigure(options => options.AddProviderName(domain));
- if (configureOptions != null)
- {
- builder.Services.Configure(domain, configureOptions);
- }
-
- builder.Services.TryAddKeyedTransient(domain, (provider, key) =>
- {
- if (key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
- return implementationFactory(provider, key.ToString()!);
- });
-
- builder.AddClient(domain);
- return builder;
- }
-
- ///
- /// Adds a feature provider for a specified domain using the default options.
- /// This method configures a feature provider without custom options, delegating to the more generic AddProvider method.
- ///
- /// The used to configure feature flags.
- /// The unique name of the provider.
- ///
- /// A factory method that creates a feature provider instance.
- /// It adds the provider as a transient service unless it is already added.
- ///
- /// The updated instance with the new feature provider configured.
- ///
- /// Thrown if either or is null or if the is empty.
- ///
- public static OpenFeatureBuilder AddProvider(this OpenFeatureBuilder builder, string domain, Func implementationFactory)
- => AddProvider(builder, domain, implementationFactory, configureOptions: null);
-
- ///
- /// Adds a feature client to the service collection, configuring it to work with a specific context if provided.
- ///
- /// The instance.
- /// Optional: The name for the feature client instance.
- /// The instance.
- internal static OpenFeatureBuilder AddClient(this OpenFeatureBuilder builder, string? name = null)
- {
- if (string.IsNullOrWhiteSpace(name))
- {
- if (builder.IsContextConfigured)
- {
- builder.Services.TryAddScoped(static provider =>
- {
- var api = provider.GetRequiredService();
- var client = api.GetClient();
- var context = provider.GetRequiredService();
- client.SetContext(context);
- return client;
- });
- }
- else
- {
- builder.Services.TryAddScoped(static provider =>
- {
- var api = provider.GetRequiredService();
- return api.GetClient();
- });
- }
- }
- else
- {
- if (builder.IsContextConfigured)
- {
- builder.Services.TryAddKeyedScoped(name, static (provider, key) =>
- {
- var api = provider.GetRequiredService();
- var client = api.GetClient(key!.ToString());
- var context = provider.GetRequiredService();
- client.SetContext(context);
- return client;
- });
- }
- else
- {
- builder.Services.TryAddKeyedScoped(name, static (provider, key) =>
- {
- var api = provider.GetRequiredService();
- return api.GetClient(key!.ToString());
- });
- }
- }
-
- return builder;
- }
-
///
/// Adds a default to the based on the policy name options.
/// This method configures the dependency injection container to resolve the appropriate
@@ -233,35 +76,6 @@ internal static OpenFeatureBuilder AddPolicyBasedClient(this OpenFeatureBuilder
return builder;
}
- ///
- /// Configures policy name options for OpenFeature using the specified options type.
- ///
- /// The type of options used to configure .
- /// The instance.
- /// A delegate to configure .
- /// The configured instance.
- /// Thrown when the or is null.
- public static OpenFeatureBuilder AddPolicyName(this OpenFeatureBuilder builder, Action configureOptions)
- where TOptions : PolicyNameOptions
- {
- Guard.ThrowIfNull(builder);
- Guard.ThrowIfNull(configureOptions);
-
- builder.IsPolicyConfigured = true;
-
- builder.Services.Configure(configureOptions);
- return builder;
- }
-
- ///
- /// Configures the default policy name options for OpenFeature.
- ///
- /// The instance.
- /// A delegate to configure .
- /// The configured instance.
- public static OpenFeatureBuilder AddPolicyName(this OpenFeatureBuilder builder, Action configureOptions)
- => AddPolicyName(builder, configureOptions);
-
///
/// Adds a feature hook to the service collection using a factory method. Hooks added here are not domain-bound.
///
diff --git a/src/OpenFeature.Hosting/OpenFeatureOptions.cs b/src/OpenFeature.Hosting/OpenFeatureOptions.cs
index 9d3dd818e..be4788378 100644
--- a/src/OpenFeature.Hosting/OpenFeatureOptions.cs
+++ b/src/OpenFeature.Hosting/OpenFeatureOptions.cs
@@ -5,48 +5,6 @@ namespace OpenFeature.Hosting;
///
public class OpenFeatureOptions
{
- private readonly HashSet _providerNames = [];
-
- ///
- /// Determines if a default provider has been registered.
- ///
- public bool HasDefaultProvider { get; private set; }
-
- ///
- /// The type of the configured feature provider.
- ///
- public Type FeatureProviderType { get; protected internal set; } = null!;
-
- ///
- /// Gets a read-only list of registered provider names.
- ///
- public IReadOnlyCollection ProviderNames => _providerNames;
-
- ///
- /// Registers the default provider name if no specific name is provided.
- /// Sets to true.
- ///
- protected internal void AddDefaultProviderName() => AddProviderName(null);
-
- ///
- /// Registers a new feature provider name. This operation is thread-safe.
- ///
- /// The name of the feature provider to register. Registers as default if null.
- protected internal void AddProviderName(string? name)
- {
- if (string.IsNullOrWhiteSpace(name))
- {
- HasDefaultProvider = true;
- }
- else
- {
- lock (_providerNames)
- {
- _providerNames.Add(name!);
- }
- }
- }
-
private readonly HashSet _hookNames = [];
internal IReadOnlyCollection HookNames => _hookNames;
diff --git a/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs b/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs
index 236dc62b0..ba2f1b0d0 100644
--- a/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs
+++ b/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
+using OpenFeature.DependencyInjection.Abstractions;
using OpenFeature.Hosting;
using OpenFeature.Hosting.Internal;
@@ -49,7 +50,7 @@ public static IServiceCollection AddOpenFeature(this IServiceCollection services
{
options.DefaultNameSelector = provider =>
{
- var options = provider.GetRequiredService>().Value;
+ var options = provider.GetRequiredService>().Value;
return options.ProviderNames.First();
};
});
diff --git a/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs b/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs
index d63009d62..68d55bb83 100644
--- a/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs
+++ b/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs
@@ -1,27 +1,28 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using OpenFeature.DependencyInjection.Abstractions;
using OpenFeature.Providers.Memory;
namespace OpenFeature.Hosting.Providers.Memory;
///
-/// Extension methods for configuring feature providers with .
+/// Extension methods for configuring feature providers with .
///
#if NET8_0_OR_GREATER
-[System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)]
+[System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Abstractions.Diagnostics.FeatureCodes.NewDi)]
#endif
public static partial class FeatureBuilderExtensions
{
///
- /// Adds an in-memory feature provider to the with a factory for flags.
+ /// Adds an in-memory feature provider to the with a factory for flags.
///
- /// The instance to configure.
+ /// The instance to configure.
///
/// A factory function to provide an of flags.
/// If null, an empty provider will be created.
///
- /// The instance for chaining.
- public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, Func?> flagsFactory)
+ /// The instance for chaining.
+ public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, Func?> flagsFactory)
=> builder.AddProvider(provider =>
{
var flags = flagsFactory(provider);
@@ -34,29 +35,29 @@ public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder bui
});
///
- /// Adds an in-memory feature provider to the with a domain and factory for flags.
+ /// Adds an in-memory feature provider to the with a domain and factory for flags.
///
- /// The instance to configure.
+ /// The instance to configure.
/// The unique domain of the provider.
///
/// A factory function to provide an of flags.
/// If null, an empty provider will be created.
///
- /// The instance for chaining.
- public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, string domain, Func?> flagsFactory)
+ /// The instance for chaining.
+ public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, string domain, Func?> flagsFactory)
=> builder.AddInMemoryProvider(domain, (provider, _) => flagsFactory(provider));
///
- /// Adds an in-memory feature provider to the with a domain and contextual flag factory.
+ /// Adds an in-memory feature provider to the with a domain and contextual flag factory.
/// If null, an empty provider will be created.
///
- /// The instance to configure.
+ /// The instance to configure.
/// The unique domain of the provider.
///
/// A factory function to provide an of flags based on service provider and domain.
///
- /// The instance for chaining.
- public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, string domain, Func?> flagsFactory)
+ /// The instance for chaining.
+ public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, string domain, Func?> flagsFactory)
=> builder.AddProvider(domain, (provider, key) =>
{
var flags = flagsFactory(provider, key);
@@ -69,28 +70,28 @@ public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder bui
});
///
- /// Adds an in-memory feature provider to the with optional flag configuration.
+ /// Adds an in-memory feature provider to the with optional flag configuration.
///
- /// The instance to configure.
+ /// The instance to configure.
///
/// An optional delegate to configure feature flags in the in-memory provider.
/// If null, an empty provider will be created.
///
- /// The instance for chaining.
- public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, Action>? configure = null)
+ /// The instance for chaining.
+ public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, Action>? configure = null)
=> builder.AddProvider(CreateProvider, options => ConfigureFlags(options, configure));
///
- /// Adds an in-memory feature provider with a specific domain to the with optional flag configuration.
+ /// Adds an in-memory feature provider with a specific domain to the with optional flag configuration.
///
- /// The instance to configure.
+ /// The instance to configure.
/// The unique domain of the provider
///
/// An optional delegate to configure feature flags in the in-memory provider.
/// If null, an empty provider will be created.
///
- /// The instance for chaining.
- public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, string domain, Action>? configure = null)
+ /// The instance for chaining.
+ public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, string domain, Action>? configure = null)
=> builder.AddProvider(domain, CreateProvider, options => ConfigureFlags(options, configure));
private static FeatureProvider CreateProvider(IServiceProvider provider, string domain)
diff --git a/src/OpenFeature.Hosting/Providers/Memory/InMemoryProviderOptions.cs b/src/OpenFeature.Hosting/Providers/Memory/InMemoryProviderOptions.cs
index 3e7431eef..15310905c 100644
--- a/src/OpenFeature.Hosting/Providers/Memory/InMemoryProviderOptions.cs
+++ b/src/OpenFeature.Hosting/Providers/Memory/InMemoryProviderOptions.cs
@@ -1,3 +1,4 @@
+using OpenFeature.DependencyInjection.Abstractions;
using OpenFeature.Providers.Memory;
namespace OpenFeature.Hosting.Providers.Memory;
@@ -5,7 +6,7 @@ namespace OpenFeature.Hosting.Providers.Memory;
///
/// Options for configuring the in-memory feature flag provider.
///
-public class InMemoryProviderOptions : OpenFeatureOptions
+public class InMemoryProviderOptions : OpenFeatureProviderOptions
{
///
/// Gets or sets the feature flags to be used by the in-memory provider.
diff --git a/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs b/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs
index 2d379fc4e..9437ae1b9 100644
--- a/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs
+++ b/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs
@@ -3,6 +3,7 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Testing;
using OpenFeature.Constant;
+using OpenFeature.DependencyInjection.Abstractions;
using OpenFeature.Hosting.Internal;
namespace OpenFeature.Hosting.Tests.Internal;
@@ -15,7 +16,7 @@ public async Task EnsureInitializedAsync_SetsProvider()
// Arrange
var services = new ServiceCollection();
var provider = new NoOpFeatureProvider();
- services.AddOptions().Configure(options =>
+ services.AddOptions().Configure(options =>
{
options.AddProviderName(null);
});
@@ -40,7 +41,7 @@ public async Task EnsureInitializedAsync_SetsMultipleProvider()
var services = new ServiceCollection();
var provider1 = new NoOpFeatureProvider();
var provider2 = new NoOpFeatureProvider();
- services.AddOptions().Configure(options =>
+ services.AddOptions().Configure(options =>
{
options.AddProviderName("provider1");
options.AddProviderName("provider2");
@@ -67,9 +68,12 @@ public async Task EnsureInitializedAsync_AddsHooks()
var services = new ServiceCollection();
var provider = new NoOpFeatureProvider();
var hook = new NoOpHook();
- services.AddOptions().Configure(options =>
+ services.AddOptions().Configure(options =>
{
options.AddProviderName(null);
+ });
+ services.AddOptions().Configure(options =>
+ {
options.AddHookName("TestHook");
});
services.AddSingleton(provider);
@@ -94,7 +98,7 @@ public async Task EnsureInitializedAsync_AddHandlers()
// Arrange
var services = new ServiceCollection();
var provider = new NoOpFeatureProvider();
- services.AddOptions().Configure(options =>
+ services.AddOptions().Configure(options =>
{
options.AddProviderName(null);
});
@@ -142,7 +146,7 @@ public async Task EnsureInitializedAsync_LogStartingInitialization()
// Arrange
var services = new ServiceCollection();
var provider = new NoOpFeatureProvider();
- services.AddOptions().Configure(options =>
+ services.AddOptions().Configure(options =>
{
options.AddProviderName(null);
});
@@ -169,7 +173,7 @@ public async Task ShutdownAsync_LogShuttingDown()
// Arrange
var services = new ServiceCollection();
var provider = new NoOpFeatureProvider();
- services.AddOptions().Configure(options =>
+ services.AddOptions().Configure(options =>
{
options.AddProviderName(null);
});
diff --git a/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs b/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs
index 1a284c918..0485eb313 100644
--- a/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs
+++ b/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using OpenFeature.DependencyInjection.Abstractions;
using OpenFeature.Hosting.Internal;
using OpenFeature.Model;
@@ -58,7 +59,7 @@ public void AddContext_Delegate_ShouldCorrectlyHandles(bool useServiceProviderDe
}
#if NET8_0_OR_GREATER
- [System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)]
+ [System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Abstractions.Diagnostics.FeatureCodes.NewDi)]
#endif
[Theory]
[InlineData(1, true, 0)]
@@ -72,8 +73,8 @@ public void AddProvider_ShouldAddProviderToCollection(int providerRegistrationTy
{
1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()),
2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()),
- 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }),
- 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }),
+ 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { o.SomeFlag = true; }),
+ 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { o.SomeFlag = true; }),
_ => throw new InvalidOperationException("Invalid mode.")
};
@@ -88,10 +89,13 @@ public void AddProvider_ShouldAddProviderToCollection(int providerRegistrationTy
serviceDescriptor.Lifetime == ServiceLifetime.Transient);
}
- class TestOptions : OpenFeatureOptions { }
+ internal sealed class TestProviderOptions : OpenFeatureProviderOptions
+ {
+ public bool SomeFlag { get; set; }
+ }
#if NET8_0_OR_GREATER
- [System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)]
+ [System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Abstractions.Diagnostics.FeatureCodes.NewDi)]
#endif
[Theory]
[InlineData(1)]
@@ -105,8 +109,8 @@ public void AddProvider_ShouldResolveCorrectProvider(int providerRegistrationTyp
{
1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()),
2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()),
- 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }),
- 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }),
+ 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }),
+ 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }),
_ => throw new InvalidOperationException("Invalid mode.")
};
@@ -149,22 +153,22 @@ public void AddProvider_VerifiesDefaultAndDomainBoundProvidersBasedOnConfigurati
.AddProvider("test1", (_, _) => new NoOpFeatureProvider())
.AddProvider("test2", (_, _) => new NoOpFeatureProvider()),
4 => _systemUnderTest
- .AddProvider(_ => new NoOpFeatureProvider(), o => { })
+ .AddProvider(_ => new NoOpFeatureProvider(), o => { })
.AddProvider("test", (_, _) => new NoOpFeatureProvider()),
5 => _systemUnderTest
- .AddProvider(_ => new NoOpFeatureProvider(), o => { })
+ .AddProvider(_ => new NoOpFeatureProvider(), o => { })
.AddProvider("test", (_, _) => new NoOpFeatureProvider()),
6 => _systemUnderTest
- .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { })
+ .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { })
.AddProvider("test2", (_, _) => new NoOpFeatureProvider()),
7 => _systemUnderTest
.AddProvider(_ => new NoOpFeatureProvider())
.AddProvider("test", (_, _) => new NoOpFeatureProvider())
.AddProvider("test2", (_, _) => new NoOpFeatureProvider()),
8 => _systemUnderTest
- .AddProvider(_ => new NoOpFeatureProvider(), o => { })
- .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { })
- .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }),
+ .AddProvider(_ => new NoOpFeatureProvider(), o => { })
+ .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { })
+ .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }),
_ => throw new InvalidOperationException("Invalid mode.")
};
@@ -203,15 +207,15 @@ public void AddProvider_ConfiguresPolicyNameAcrossMultipleProviderSetups(int pro
.AddProvider("test2", (_, _) => new NoOpFeatureProvider())
.AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName),
4 => _systemUnderTest
- .AddProvider(_ => new NoOpFeatureProvider(), o => { })
+ .AddProvider(_ => new NoOpFeatureProvider(), o => { })
.AddProvider("test", (_, _) => new NoOpFeatureProvider())
.AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName),
5 => _systemUnderTest
- .AddProvider(_ => new NoOpFeatureProvider(), o => { })
+ .AddProvider(_ => new NoOpFeatureProvider(), o => { })
.AddProvider("test", (_, _) => new NoOpFeatureProvider())
.AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName),
6 => _systemUnderTest
- .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { })
+ .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { })
.AddProvider("test2", (_, _) => new NoOpFeatureProvider())
.AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName),
7 => _systemUnderTest
@@ -220,9 +224,9 @@ public void AddProvider_ConfiguresPolicyNameAcrossMultipleProviderSetups(int pro
.AddProvider("test2", (_, _) => new NoOpFeatureProvider())
.AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName),
8 => _systemUnderTest
- .AddProvider(_ => new NoOpFeatureProvider(), o => { })
- .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { })
- .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { })
+ .AddProvider(_ => new NoOpFeatureProvider(), o => { })
+ .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { })
+ .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { })
.AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName),
_ => throw new InvalidOperationException("Invalid mode.")
};
diff --git a/test/OpenFeature.Hosting.Tests/OpenFeatureOptionsTests.cs b/test/OpenFeature.Hosting.Tests/OpenFeatureProviderOptionsTests.cs
similarity index 81%
rename from test/OpenFeature.Hosting.Tests/OpenFeatureOptionsTests.cs
rename to test/OpenFeature.Hosting.Tests/OpenFeatureProviderOptionsTests.cs
index d39d4059f..7a254ec1a 100644
--- a/test/OpenFeature.Hosting.Tests/OpenFeatureOptionsTests.cs
+++ b/test/OpenFeature.Hosting.Tests/OpenFeatureProviderOptionsTests.cs
@@ -1,12 +1,14 @@
+using OpenFeature.DependencyInjection.Abstractions;
+
namespace OpenFeature.Hosting.Tests;
-public class OpenFeatureOptionsTests
+public class OpenFeatureProviderOptionsTests
{
[Fact]
public void AddProviderName_DoesNotSetHasDefaultProvider()
{
// Arrange
- var options = new OpenFeatureOptions();
+ var options = new OpenFeatureProviderOptions();
// Act
options.AddProviderName("TestProvider");
@@ -19,7 +21,7 @@ public void AddProviderName_DoesNotSetHasDefaultProvider()
public void AddProviderName_WithNullName_SetsHasDefaultProvider()
{
// Arrange
- var options = new OpenFeatureOptions();
+ var options = new OpenFeatureProviderOptions();
// Act
options.AddProviderName(null);
@@ -34,7 +36,7 @@ public void AddProviderName_WithNullName_SetsHasDefaultProvider()
public void AddProviderName_WithEmptyName_SetsHasDefaultProvider(string name)
{
// Arrange
- var options = new OpenFeatureOptions();
+ var options = new OpenFeatureProviderOptions();
// Act
options.AddProviderName(name);
@@ -47,7 +49,7 @@ public void AddProviderName_WithEmptyName_SetsHasDefaultProvider(string name)
public void AddProviderName_WithSameName_OnlyRegistersNameOnce()
{
// Arrange
- var options = new OpenFeatureOptions();
+ var options = new OpenFeatureProviderOptions();
// Act
options.AddProviderName("test-provider");
diff --git a/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs b/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs
index 9638ff8c1..82a286cca 100644
--- a/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs
+++ b/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs
@@ -224,8 +224,8 @@ private static async Task CreateServerAsync(ServiceLifetime serviceL
{
if (serviceLifetime == ServiceLifetime.Scoped)
{
- using var scoped = provider.CreateScope();
- var flagService = scoped.ServiceProvider.GetRequiredService();
+ using var scope = provider.CreateScope();
+ var flagService = scope.ServiceProvider.GetRequiredService();
return flagService.GetFlags();
}
else