diff --git a/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs b/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs index 74d01ad3a..a24c67e78 100644 --- a/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs +++ b/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs @@ -24,7 +24,9 @@ public static IServiceCollection AddOpenFeature(this IServiceCollection services Guard.ThrowIfNull(configure); // Register core OpenFeature services as singletons. - services.TryAddSingleton(Api.Instance); + var api = new Api(); + Api.SetInstance(api); + services.TryAddSingleton(api); services.TryAddSingleton(); var builder = new OpenFeatureBuilder(services); diff --git a/src/OpenFeature/Api.cs b/src/OpenFeature/Api.cs index 93deb31cb..e4a9826c5 100644 --- a/src/OpenFeature/Api.cs +++ b/src/OpenFeature/Api.cs @@ -32,7 +32,7 @@ public sealed class Api : IEventBus // not to mark type as beforeFieldInit // IE Lazy way of ensuring this is thread safe without using locks static Api() { } - private Api() { } + internal Api() { } /// /// Sets the default feature provider. In order to wait for the provider to be set, and initialization to complete, @@ -121,7 +121,7 @@ public FeatureProvider GetProvider(string domain) /// public FeatureClient GetClient(string? name = null, string? version = null, ILogger? logger = null, EvaluationContext? context = null) => - new FeatureClient(() => this._repository.GetProvider(name), name, version, logger, context); + new FeatureClient(this, () => this._repository.GetProvider(name), name, version, logger, context); /// /// Appends list of hooks to global hooks list @@ -360,4 +360,12 @@ internal static void ResetApi() { Instance = new Api(); } + + /// + /// This method should only be used in the Dependency Injection setup. It will set the singleton instance of the API using the provided instance. + /// + internal static void SetInstance(Api api) + { + Instance = api; + } } diff --git a/src/OpenFeature/OpenFeature.csproj b/src/OpenFeature/OpenFeature.csproj index 2a733157d..2b1983959 100644 --- a/src/OpenFeature/OpenFeature.csproj +++ b/src/OpenFeature/OpenFeature.csproj @@ -19,7 +19,8 @@ + - + \ No newline at end of file diff --git a/src/OpenFeature/OpenFeatureClient.cs b/src/OpenFeature/OpenFeatureClient.cs index c99f4f5c9..1f47d2d24 100644 --- a/src/OpenFeature/OpenFeatureClient.cs +++ b/src/OpenFeature/OpenFeatureClient.cs @@ -18,6 +18,7 @@ public sealed partial class FeatureClient : IFeatureClient private readonly ConcurrentStack _hooks = new ConcurrentStack(); private readonly ILogger _logger; private readonly Func _providerAccessor; + private readonly Api _api; private EvaluationContext _evaluationContext; private readonly object _evaluationContextLock = new object(); @@ -40,7 +41,7 @@ public sealed partial class FeatureClient : IFeatureClient { // Alias the provider reference so getting the method and returning the provider are // guaranteed to be the same object. - var provider = Api.Instance.GetProvider(this._metadata.Name!); + var provider = this._api.GetProvider(this._metadata.Name!); return (method(provider), provider); } @@ -69,18 +70,20 @@ public void SetContext(EvaluationContext? context) /// /// Initializes a new instance of the class. /// + /// The API instance for accessing global state and providers /// Function to retrieve current provider /// Name of client /// Version of client /// Logger used by client /// Context given to this client /// Throws if any of the required parameters are null - internal FeatureClient(Func providerAccessor, string? name, string? version, ILogger? logger = null, EvaluationContext? context = null) + internal FeatureClient(Api api, Func providerAccessor, string? name, string? version, ILogger? logger = null, EvaluationContext? context = null) { + this._api = api; + this._providerAccessor = providerAccessor; this._metadata = new ClientMetadata(name, version); this._logger = logger ?? NullLogger.Instance; this._evaluationContext = context ?? EvaluationContext.Empty; - this._providerAccessor = providerAccessor; } /// @@ -99,13 +102,13 @@ internal FeatureClient(Func providerAccessor, string? name, str /// public void AddHandler(ProviderEventTypes eventType, EventHandlerDelegate handler) { - Api.Instance.AddClientHandler(this._metadata.Name!, eventType, handler); + this._api.AddClientHandler(this._metadata.Name!, eventType, handler); } /// public void RemoveHandler(ProviderEventTypes type, EventHandlerDelegate handler) { - Api.Instance.RemoveClientHandler(this._metadata.Name!, type, handler); + this._api.RemoveClientHandler(this._metadata.Name!, type, handler); } /// @@ -213,13 +216,13 @@ private async Task> EvaluateFlagAsync( // merge api, client, transaction and invocation context var evaluationContextBuilder = EvaluationContext.Builder(); - evaluationContextBuilder.Merge(Api.Instance.GetContext()); // API context + evaluationContextBuilder.Merge(this._api.GetContext()); // API context evaluationContextBuilder.Merge(this.GetContext()); // Client context - evaluationContextBuilder.Merge(Api.Instance.GetTransactionContext()); // Transaction context + evaluationContextBuilder.Merge(this._api.GetTransactionContext()); // Transaction context evaluationContextBuilder.Merge(context); // Invocation context var allHooks = ImmutableList.CreateBuilder() - .Concat(Api.Instance.GetHooks()) + .Concat(this._api.GetHooks()) .Concat(this.GetHooks()) .Concat(options?.Hooks ?? Enumerable.Empty()) .Concat(provider.GetProviderHooks()) @@ -310,7 +313,7 @@ public void Track(string trackingEventName, EvaluationContext? evaluationContext throw new ArgumentException("Tracking event cannot be null or empty.", nameof(trackingEventName)); } - var globalContext = Api.Instance.GetContext(); + var globalContext = this._api.GetContext(); var clientContext = this.GetContext(); var evaluationContextBuilder = EvaluationContext.Builder()