diff --git a/.github/workflows/aot-compatibility.yml b/.github/workflows/aot-compatibility.yml
index 76e16c246..20ca24a22 100644
--- a/.github/workflows/aot-compatibility.yml
+++ b/.github/workflows/aot-compatibility.yml
@@ -34,7 +34,7 @@ jobs:
arch: arm64
runtime: win-arm64
# macOS x64
- - os: macos-13
+ - os: macos-15-intel
arch: x64
runtime: osx-x64
# macOS ARM64 (Apple Silicon)
@@ -46,13 +46,13 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
+ uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
with:
fetch-depth: 0
submodules: recursive
- name: Setup .NET SDK
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
+ uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5
with:
global-json-file: global.json
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f62e3cd44..6f3f5c8eb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,13 +23,13 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
+ uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
with:
fetch-depth: 0
submodules: recursive
- name: Setup .NET SDK
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
+ uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5
with:
global-json-file: global.json
@@ -67,13 +67,13 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
+ uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
with:
fetch-depth: 0
submodules: recursive
- name: Setup .NET SDK
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
+ uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5
with:
global-json-file: global.json
@@ -102,7 +102,7 @@ jobs:
- name: Publish NuGet packages (fork)
if: github.event.pull_request.head.repo.fork == true
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: nupkgs
path: src/**/*.nupkg
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
index e6c2518c0..67d2e629b 100644
--- a/.github/workflows/code-coverage.yml
+++ b/.github/workflows/code-coverage.yml
@@ -22,12 +22,12 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
with:
fetch-depth: 0
- name: Setup .NET SDK
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
+ uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5
with:
global-json-file: global.json
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 74378bfc9..ee4dc7f53 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -38,11 +38,11 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
+ uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4
+ uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4
+ uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -69,4 +69,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4
+ uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4
diff --git a/.github/workflows/dotnet-format.yml b/.github/workflows/dotnet-format.yml
index f7857ea64..170886165 100644
--- a/.github/workflows/dotnet-format.yml
+++ b/.github/workflows/dotnet-format.yml
@@ -15,10 +15,10 @@ jobs:
steps:
- name: Check out code
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
+ uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
- name: Setup .NET SDK
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
+ uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5
with:
global-json-file: global.json
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index da2edad27..d975ca0bd 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -17,12 +17,12 @@ jobs:
contents: read
pull-requests: write
steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
with:
fetch-depth: 0
- name: Setup .NET SDK
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
+ uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5
with:
global-json-file: global.json
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index caf7f9e6f..7cd116145 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: googleapis/release-please-action@c2a5a2bd6a758a0937f1ddb1e8950609867ed15c # v4
+ - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4
id: release
with:
token: ${{secrets.RELEASE_PLEASE_ACTION_TOKEN}}
@@ -39,12 +39,12 @@ jobs:
if: ${{ fromJSON(needs.release-please.outputs.release_created || false) }}
steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
with:
fetch-depth: 0
- name: Setup .NET SDK
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5
+ uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5
with:
global-json-file: global.json
@@ -93,14 +93,6 @@ jobs:
project-name: OpenFeature.Hosting
release-tag: ${{ needs.release-please.outputs.release_tag_name }}
- # Process OpenFeature.DependencyInjection project
- - name: Generate and Attest SBOM for OpenFeature.DependencyInjection
- uses: ./.github/actions/sbom-generator
- with:
- github-token: ${{secrets.GITHUB_TOKEN}}
- project-name: OpenFeature.DependencyInjection
- release-tag: ${{ needs.release-please.outputs.release_tag_name }}
-
# Process OpenFeature.Providers.MultiProvider project
- name: Generate and Attest SBOM for OpenFeature.Providers.MultiProvider
uses: ./.github/actions/sbom-generator
diff --git a/.gitignore b/.gitignore
index c77e4f530..5648ca1dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -351,5 +351,7 @@ ASALocalRun/
# integration tests
test/OpenFeature.E2ETests/Features/*.feature
test/OpenFeature.E2ETests/Features/*.feature.cs
+test/OpenFeature.E2ETests/Features/README.md
+test/OpenFeature.E2ETests/Features/test-flags.json
cs-report.json
specification.json
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index a3906fc08..f393718c9 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "2.9.0"
+ ".": "2.10.0"
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bdbe5b608..59501e851 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,32 @@
# Changelog
+## [2.10.0](https://github.com/open-feature/dotnet-sdk/compare/v2.9.0...v2.10.0) (2025-12-01)
+
+
+### 🐛 Bug Fixes
+
+* Address issue with FeatureClient not being resolved when no Provider added ([#607](https://github.com/open-feature/dotnet-sdk/issues/607)) ([a8d12ef](https://github.com/open-feature/dotnet-sdk/commit/a8d12ef12d75aaa770551b3052cd8725b65b5fd8))
+* Address issues when evaluating the context in the InMemoryProvider ([#615](https://github.com/open-feature/dotnet-sdk/issues/615)) ([94fcdc1](https://github.com/open-feature/dotnet-sdk/commit/94fcdc142c61f41619af222778d6d84264f2831c))
+* Ensure AddPolicyName without adding a Provider does not get stuck in infinite loop ([#606](https://github.com/open-feature/dotnet-sdk/issues/606)) ([4b965dd](https://github.com/open-feature/dotnet-sdk/commit/4b965dddcaeef761e01f8fcbd28941ae3f3074c9))
+* Ensure EvaluationContext is reliably added to the injected FeatureClient ([#605](https://github.com/open-feature/dotnet-sdk/issues/605)) ([c987b58](https://github.com/open-feature/dotnet-sdk/commit/c987b58b66c8186486fd06aebdc4042052f30beb))
+
+
+### ✨ New Features
+
+* Add DI for multi provider ([#621](https://github.com/open-feature/dotnet-sdk/issues/621)) ([ee862f0](https://github.com/open-feature/dotnet-sdk/commit/ee862f09cb2c58f43f84957fa95e8b25e8e36f72))
+* Add disabled flag support to InMemoryProvider ([#632](https://github.com/open-feature/dotnet-sdk/issues/632)) ([df1765c](https://github.com/open-feature/dotnet-sdk/commit/df1765c7abc4e9e5f76954ddb361b3fd5bf0ddf7))
+* Add optional CancellationToken parameter to SetProviderAsync ([#638](https://github.com/open-feature/dotnet-sdk/issues/638)) ([a1f7ff6](https://github.com/open-feature/dotnet-sdk/commit/a1f7ff6434842ff051e32af5c787e1bf40a5cb66))
+* Add SourceLink configuration for .NET SDK 8+ to enhance debugging experience ([1b40391](https://github.com/open-feature/dotnet-sdk/commit/1b40391034b0762aa755a05374a908eb97cdf444))
+* Add SourceLink configuration for .NET to enhance debugging experience ([#614](https://github.com/open-feature/dotnet-sdk/issues/614)) ([1b40391](https://github.com/open-feature/dotnet-sdk/commit/1b40391034b0762aa755a05374a908eb97cdf444))
+* Add tracking to multi-provider ([#612](https://github.com/open-feature/dotnet-sdk/issues/612)) ([186b357](https://github.com/open-feature/dotnet-sdk/commit/186b3574702258fb33716162094888b9f7560c7c))
+
+
+### 🔧 Refactoring
+
+* Clean up project files by removing TargetFrameworks and formatting ([#611](https://github.com/open-feature/dotnet-sdk/issues/611)) ([dfbc3ee](https://github.com/open-feature/dotnet-sdk/commit/dfbc3eef1f7468dc363c71fef1eb1f42e1bb8a88))
+* Pass cancellation tokens to Provider Initialization functions ([#640](https://github.com/open-feature/dotnet-sdk/issues/640)) ([8b472d8](https://github.com/open-feature/dotnet-sdk/commit/8b472d8ccd1367ba82a2ab39ad7a77b1a6609ce0))
+* Remove deprecated Dependency Injection code ([#626](https://github.com/open-feature/dotnet-sdk/issues/626)) ([a36a906](https://github.com/open-feature/dotnet-sdk/commit/a36a9067102a70f80e7837ce18d287430c7452fc))
+
## [2.9.0](https://github.com/open-feature/dotnet-sdk/compare/v2.8.1...v2.9.0) (2025-10-16)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 0f6a8448b..58b3afa57 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -30,20 +30,27 @@
-
+
-
+
-
+
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
diff --git a/LICENSE b/LICENSE
index 261eeb9e9..96b3dc8fc 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright OpenFeature Maintainers
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/OpenFeature.slnx b/OpenFeature.slnx
index 936079f40..db8f40024 100644
--- a/OpenFeature.slnx
+++ b/OpenFeature.slnx
@@ -48,7 +48,6 @@
-
@@ -58,7 +57,6 @@
-
diff --git a/README.md b/README.md
index 4eb02b551..ae4253285 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,8 @@
[](https://github.com/open-feature/spec/releases/tag/v0.8.0)
[
-
-](https://github.com/open-feature/dotnet-sdk/releases/tag/v2.9.0)
+
+](https://github.com/open-feature/dotnet-sdk/releases/tag/v2.10.0)
[](https://cloud-native.slack.com/archives/C0344AANLA1)
[](https://codecov.io/gh/open-feature/dotnet-sdk)
diff --git a/build/Common.prod.props b/build/Common.prod.props
index f1a21cc26..bfe58d3d1 100644
--- a/build/Common.prod.props
+++ b/build/Common.prod.props
@@ -9,7 +9,7 @@
- 2.9.0
+ 2.10.0githttps://github.com/open-feature/dotnet-sdkOpenFeature is an open standard for feature flag management, created to support a robust feature flag ecosystem using cloud native technologies. OpenFeature will provide a unified API and SDK, and a developer-first, cloud-native implementation, with extensibility for open source and commercial offerings.
@@ -33,4 +33,16 @@
+
+
+
+
+ true
+
+ true
+
+ true
+ snupkg
+
+
diff --git a/build/Common.props b/build/Common.props
index 287b32312..41df868d0 100644
--- a/build/Common.props
+++ b/build/Common.props
@@ -28,4 +28,11 @@
+
+
+
+ true
+
diff --git a/release-please-config.json b/release-please-config.json
index 6baeed441..1f778ed73 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -8,8 +8,7 @@
"versioning": "default",
"extra-files": [
"build/Common.prod.props",
- "README.md",
- "src/OpenFeature.DependencyInjection/README.md"
+ "README.md"
]
}
},
diff --git a/samples/AspNetCore/Program.cs b/samples/AspNetCore/Program.cs
index 3dc0203b1..87238c158 100644
--- a/samples/AspNetCore/Program.cs
+++ b/samples/AspNetCore/Program.cs
@@ -7,6 +7,7 @@
using OpenFeature.Model;
using OpenFeature.Providers.Memory;
using OpenFeature.Providers.MultiProvider;
+using OpenFeature.Providers.MultiProvider.DependencyInjection;
using OpenFeature.Providers.MultiProvider.Models;
using OpenFeature.Providers.MultiProvider.Strategies;
using OpenTelemetry.Metrics;
@@ -51,6 +52,11 @@
"welcome-message", new Flag(
new Dictionary { { "show", true }, { "hide", false } }, "show")
},
+ {
+ "disabled-flag", new Flag(
+ new Dictionary { { "on", "This flag is on" }, { "off", "This flag is off" } }, "off",
+ disabled: true)
+ },
{
"test-config", new Flag(new Dictionary()
{
@@ -59,7 +65,28 @@
{ "disable", new Value(Structure.Builder().Set(nameof(TestConfig.Threshold), 0).Build()) }
}, "disable")
}
- });
+ })
+ .AddMultiProvider("multi-provider", multiProviderBuilder =>
+ {
+ // Create provider flags
+ var provider1Flags = new Dictionary
+ {
+ { "providername", new Flag(new Dictionary { { "enabled", "enabled-provider1" }, { "disabled", "disabled-provider1" } }, "enabled") },
+ { "max-items", new Flag(new Dictionary { { "low", 10 }, { "high", 100 } }, "high") },
+ };
+
+ var provider2Flags = new Dictionary
+ {
+ { "providername", new Flag(new Dictionary { { "enabled", "enabled-provider2" }, { "disabled", "disabled-provider2" } }, "enabled") },
+ };
+
+ // Use the factory pattern to create providers - they will be properly initialized
+ multiProviderBuilder
+ .AddProvider("p1", sp => new InMemoryProvider(provider1Flags))
+ .AddProvider("p2", sp => new InMemoryProvider(provider2Flags))
+ .UseStrategy();
+ })
+ .AddPolicyName(policy => policy.DefaultNameSelector = provider => "InMemory");
});
var app = builder.Build();
@@ -139,6 +166,25 @@
}
});
+app.MapGet("/multi-provider-di", async ([FromKeyedServices("multi-provider")] IFeatureClient featureClient) =>
+{
+ try
+ {
+ // Test flag evaluation from different providers
+ var maxItemsFlag = await featureClient.GetIntegerDetailsAsync("max-items", 0);
+ var providerNameFlag = await featureClient.GetStringDetailsAsync("providername", "default");
+
+ // Test a flag that doesn't exist in any provider
+ var unknownFlag = await featureClient.GetBooleanDetailsAsync("unknown-flag", false);
+
+ return Results.Ok();
+ }
+ catch (Exception ex)
+ {
+ return Results.Problem($"Error: {ex.Message}\n\nStack: {ex.StackTrace}");
+ }
+});
+
app.Run();
diff --git a/samples/AspNetCore/Samples.AspNetCore.csproj b/samples/AspNetCore/Samples.AspNetCore.csproj
index 6945e6692..723bbd4bd 100644
--- a/samples/AspNetCore/Samples.AspNetCore.csproj
+++ b/samples/AspNetCore/Samples.AspNetCore.csproj
@@ -13,9 +13,9 @@
-
-
-
+
+
+
diff --git a/spec b/spec
index 969e11c4d..3fc2e4949 160000
--- a/spec
+++ b/spec
@@ -1 +1 @@
-Subproject commit 969e11c4d5df4ab16b400965ef1b3e313dcb923e
+Subproject commit 3fc2e4949e53f761daf3e1f7197678481015fdf0
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 3b7879044..e5439c49b 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,7 +1,10 @@
-
+
-
+ net462;netstandard2.0;net8.0;net9.0
+
$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))
-
+
\ No newline at end of file
diff --git a/src/OpenFeature.DependencyInjection/Diagnostics/FeatureCodes.cs b/src/OpenFeature.DependencyInjection/Diagnostics/FeatureCodes.cs
deleted file mode 100644
index 582ab39c9..000000000
--- a/src/OpenFeature.DependencyInjection/Diagnostics/FeatureCodes.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-namespace OpenFeature.DependencyInjection.Diagnostics;
-
-///
-/// Contains identifiers for experimental features and diagnostics in the OpenFeature framework.
-///
-///
-/// Experimental - This class includes identifiers that allow developers to track and conditionally enable
-/// experimental features. Each identifier follows a structured code format to indicate the feature domain,
-/// maturity level, and unique identifier. Note that experimental features are subject to change or removal
-/// in future releases.
-///
-/// Basic Information
-/// These identifiers conform to OpenFeature’s Diagnostics Specifications, allowing developers to recognize
-/// and manage experimental features effectively.
-///
-///
-///
-///
-/// Code Structure:
-/// - "OF" - Represents the OpenFeature library.
-/// - "DI" - Indicates the Dependency Injection domain.
-/// - "001" - Unique identifier for a specific feature.
-///
-///
-internal static class FeatureCodes
-{
- ///
- /// Identifier for the experimental Dependency Injection features within the OpenFeature framework.
- ///
- ///
- /// OFDI001 identifier marks experimental features in the Dependency Injection (DI) domain.
- ///
- /// Usage:
- /// Developers can use this identifier to conditionally enable or test experimental DI features.
- /// It is part of the OpenFeature diagnostics system to help track experimental functionality.
- ///
- public const string NewDi = "OFDI001";
-}
diff --git a/src/OpenFeature.DependencyInjection/Guard.cs b/src/OpenFeature.DependencyInjection/Guard.cs
deleted file mode 100644
index 337a8290f..000000000
--- a/src/OpenFeature.DependencyInjection/Guard.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-
-namespace OpenFeature.DependencyInjection;
-
-[DebuggerStepThrough]
-internal static class Guard
-{
- public static void ThrowIfNull(object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
- {
- if (argument is null)
- throw new ArgumentNullException(paramName);
- }
-
- public static void ThrowIfNullOrWhiteSpace(string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
- {
- if (string.IsNullOrWhiteSpace(argument))
- throw new ArgumentNullException(paramName);
- }
-}
diff --git a/src/OpenFeature.DependencyInjection/IFeatureLifecycleManager.cs b/src/OpenFeature.DependencyInjection/IFeatureLifecycleManager.cs
deleted file mode 100644
index 4891f2e8b..000000000
--- a/src/OpenFeature.DependencyInjection/IFeatureLifecycleManager.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace OpenFeature.DependencyInjection;
-
-///
-/// Defines the contract for managing the lifecycle of a feature api.
-///
-public interface IFeatureLifecycleManager
-{
- ///
- /// Ensures that the feature provider is properly initialized and ready to be used.
- /// This method should handle all necessary checks, configuration, and setup required to prepare the feature provider.
- ///
- /// Propagates notification that operations should be canceled.
- /// A Task representing the asynchronous operation of initializing the feature provider.
- /// Thrown when the feature provider is not registered or is in an invalid state.
- ValueTask EnsureInitializedAsync(CancellationToken cancellationToken = default);
-
- ///
- /// Gracefully shuts down the feature api, ensuring all resources are properly disposed of and any persistent state is saved.
- /// This method should handle all necessary cleanup and shutdown operations for the feature provider.
- ///
- /// Propagates notification that operations should be canceled.
- /// A Task representing the asynchronous operation of shutting down the feature provider.
- ValueTask ShutdownAsync(CancellationToken cancellationToken = default);
-}
diff --git a/src/OpenFeature.DependencyInjection/Internal/EventHandlerDelegateWrapper.cs b/src/OpenFeature.DependencyInjection/Internal/EventHandlerDelegateWrapper.cs
deleted file mode 100644
index d31b3355c..000000000
--- a/src/OpenFeature.DependencyInjection/Internal/EventHandlerDelegateWrapper.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using OpenFeature.Constant;
-using OpenFeature.Model;
-
-namespace OpenFeature.DependencyInjection.Internal;
-
-internal record EventHandlerDelegateWrapper(
- ProviderEventTypes ProviderEventType,
- EventHandlerDelegate EventHandlerDelegate);
diff --git a/src/OpenFeature.DependencyInjection/Internal/FeatureLifecycleManager.cs b/src/OpenFeature.DependencyInjection/Internal/FeatureLifecycleManager.cs
deleted file mode 100644
index 1ecac4349..000000000
--- a/src/OpenFeature.DependencyInjection/Internal/FeatureLifecycleManager.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-
-namespace OpenFeature.DependencyInjection.Internal;
-
-internal sealed partial class FeatureLifecycleManager : IFeatureLifecycleManager
-{
- private readonly Api _featureApi;
- private readonly IServiceProvider _serviceProvider;
- private readonly ILogger _logger;
-
- public FeatureLifecycleManager(Api featureApi, IServiceProvider serviceProvider, ILogger logger)
- {
- _featureApi = featureApi;
- _serviceProvider = serviceProvider;
- _logger = logger;
- }
-
- ///
- public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToken = default)
- {
- this.LogStartingInitializationOfFeatureProvider();
-
- var options = _serviceProvider.GetRequiredService>().Value;
- if (options.HasDefaultProvider)
- {
- var featureProvider = _serviceProvider.GetRequiredService();
- await _featureApi.SetProviderAsync(featureProvider).ConfigureAwait(false);
- }
-
- foreach (var name in options.ProviderNames)
- {
- var featureProvider = _serviceProvider.GetRequiredKeyedService(name);
- await _featureApi.SetProviderAsync(name, featureProvider).ConfigureAwait(false);
- }
-
- var hooks = new List();
- foreach (var hookName in options.HookNames)
- {
- var hook = _serviceProvider.GetRequiredKeyedService(hookName);
- hooks.Add(hook);
- }
-
- _featureApi.AddHooks(hooks);
-
- var handlers = _serviceProvider.GetServices();
- foreach (var handler in handlers)
- {
- _featureApi.AddHandler(handler.ProviderEventType, handler.EventHandlerDelegate);
- }
- }
-
- ///
- public async ValueTask ShutdownAsync(CancellationToken cancellationToken = default)
- {
- this.LogShuttingDownFeatureProvider();
- await _featureApi.ShutdownAsync().ConfigureAwait(false);
- }
-
- [LoggerMessage(200, LogLevel.Information, "Starting initialization of the feature provider")]
- partial void LogStartingInitializationOfFeatureProvider();
-
- [LoggerMessage(200, LogLevel.Information, "Shutting down the feature provider")]
- partial void LogShuttingDownFeatureProvider();
-}
diff --git a/src/OpenFeature.DependencyInjection/MultiTarget/CallerArgumentExpressionAttribute.cs b/src/OpenFeature.DependencyInjection/MultiTarget/CallerArgumentExpressionAttribute.cs
deleted file mode 100644
index afbec6b06..000000000
--- a/src/OpenFeature.DependencyInjection/MultiTarget/CallerArgumentExpressionAttribute.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-// @formatter:off
-// ReSharper disable All
-#if NETCOREAPP3_0_OR_GREATER
-// https://github.com/dotnet/runtime/issues/96197
-[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))]
-#else
-#pragma warning disable
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace System.Runtime.CompilerServices;
-
-[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
-internal sealed class CallerArgumentExpressionAttribute : Attribute
-{
- public CallerArgumentExpressionAttribute(string parameterName)
- {
- ParameterName = parameterName;
- }
-
- public string ParameterName { get; }
-}
-#endif
diff --git a/src/OpenFeature.DependencyInjection/MultiTarget/IsExternalInit.cs b/src/OpenFeature.DependencyInjection/MultiTarget/IsExternalInit.cs
deleted file mode 100644
index 877141115..000000000
--- a/src/OpenFeature.DependencyInjection/MultiTarget/IsExternalInit.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// @formatter:off
-// ReSharper disable All
-#if NET5_0_OR_GREATER
-// https://github.com/dotnet/runtime/issues/96197
-[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))]
-#else
-#pragma warning disable
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.ComponentModel;
-
-namespace System.Runtime.CompilerServices;
-
-///
-/// Reserved to be used by the compiler for tracking metadata.
-/// This class should not be used by developers in source code.
-///
-[EditorBrowsable(EditorBrowsableState.Never)]
-static class IsExternalInit { }
-#endif
diff --git a/src/OpenFeature.DependencyInjection/OpenFeature.DependencyInjection.csproj b/src/OpenFeature.DependencyInjection/OpenFeature.DependencyInjection.csproj
deleted file mode 100644
index 9ae3029df..000000000
--- a/src/OpenFeature.DependencyInjection/OpenFeature.DependencyInjection.csproj
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- netstandard2.0;net8.0;net9.0;net462
- OpenFeature.DependencyInjection
- README.md
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/OpenFeature.DependencyInjection/OpenFeatureBuilder.cs b/src/OpenFeature.DependencyInjection/OpenFeatureBuilder.cs
deleted file mode 100644
index ae1e8c8fb..000000000
--- a/src/OpenFeature.DependencyInjection/OpenFeatureBuilder.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace OpenFeature.DependencyInjection;
-
-///
-/// Describes a backed by an .
-///
-/// The services being configured.
-public class OpenFeatureBuilder(IServiceCollection 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
- /// should be initialized based on the presence of an evaluation context.
- ///
- 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.
- ///
- 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; }
-
- ///
- /// 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)
- {
- 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.");
- }
- }
- }
-}
diff --git a/src/OpenFeature.DependencyInjection/OpenFeatureBuilderExtensions.cs b/src/OpenFeature.DependencyInjection/OpenFeatureBuilderExtensions.cs
deleted file mode 100644
index d676dc5e9..000000000
--- a/src/OpenFeature.DependencyInjection/OpenFeatureBuilderExtensions.cs
+++ /dev/null
@@ -1,382 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Options;
-using OpenFeature.Constant;
-using OpenFeature.DependencyInjection;
-using OpenFeature.DependencyInjection.Internal;
-using OpenFeature.Model;
-
-namespace OpenFeature;
-
-///
-/// Contains extension methods for the class.
-///
-#if NET8_0_OR_GREATER
-[System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Diagnostics.FeatureCodes.NewDi)]
-#endif
-public static partial class OpenFeatureBuilderExtensions
-{
- ///
- /// This method is used to add a new context to the service collection.
- ///
- /// The instance.
- /// the desired configuration
- /// The instance.
- /// Thrown when the or action is null.
- public static OpenFeatureBuilder AddContext(this OpenFeatureBuilder builder, Action configure)
- {
- Guard.ThrowIfNull(builder);
- Guard.ThrowIfNull(configure);
-
- return builder.AddContext((b, _) => configure(b));
- }
-
- ///
- /// This method is used to add a new context to the service collection.
- ///
- /// The instance.
- /// the desired configuration
- /// The instance.
- /// Thrown when the or action is null.
- public static OpenFeatureBuilder AddContext(this OpenFeatureBuilder builder, Action configure)
- {
- Guard.ThrowIfNull(builder);
- Guard.ThrowIfNull(configure);
-
- builder.IsContextConfigured = true;
- builder.Services.TryAddTransient(provider =>
- {
- var contextBuilder = EvaluationContext.Builder();
- configure(contextBuilder, provider);
- return contextBuilder.Build();
- });
-
- 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
- /// depending on the policy name selected.
- /// If no name is selected (i.e., null), it retrieves the default client.
- ///
- /// The instance.
- /// The configured instance.
- internal static OpenFeatureBuilder AddPolicyBasedClient(this OpenFeatureBuilder builder)
- {
- builder.Services.AddScoped(provider =>
- {
- var policy = provider.GetRequiredService>().Value;
- var name = policy.DefaultNameSelector(provider);
- if (name == null)
- {
- return provider.GetRequiredService();
- }
- return provider.GetRequiredKeyedService(name);
- });
-
- 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.
- ///
- /// The type of to be added.
- /// The instance.
- /// Optional factory for controlling how will be created in the DI container.
- /// The instance.
- public static OpenFeatureBuilder AddHook<
-#if NET
- [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-#endif
- THook>(this OpenFeatureBuilder builder, Func? implementationFactory = null)
- where THook : Hook
- {
- return builder.AddHook(typeof(THook).Name, implementationFactory);
- }
-
- ///
- /// Adds a feature hook to the service collection. Hooks added here are not domain-bound.
- ///
- /// The type of to be added.
- /// The instance.
- /// Instance of Hook to inject into the OpenFeature context.
- /// The instance.
- public static OpenFeatureBuilder AddHook<
-#if NET
- [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-#endif
- THook>(this OpenFeatureBuilder builder, THook hook)
- where THook : Hook
- {
- return builder.AddHook(typeof(THook).Name, hook);
- }
-
- ///
- /// Adds a feature hook to the service collection with a specified name. Hooks added here are not domain-bound.
- ///
- /// The type of to be added.
- /// The instance.
- /// The name of the that is being added.
- /// Instance of Hook to inject into the OpenFeature context.
- /// The instance.
- public static OpenFeatureBuilder AddHook<
-#if NET
- [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-#endif
- THook>(this OpenFeatureBuilder builder, string hookName, THook hook)
- where THook : Hook
- {
- return builder.AddHook(hookName, _ => hook);
- }
-
- ///
- /// Adds a feature hook to the service collection using a factory method and specified name. Hooks added here are not domain-bound.
- ///
- /// The type of to be added.
- /// The instance.
- /// The name of the that is being added.
- /// Optional factory for controlling how will be created in the DI container.
- /// The instance.
- public static OpenFeatureBuilder AddHook<
-#if NET
- [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-#endif
- THook>
- (this OpenFeatureBuilder builder, string hookName, Func? implementationFactory = null)
- where THook : Hook
- {
- builder.Services.PostConfigure(options => options.AddHookName(hookName));
-
- if (implementationFactory is not null)
- {
- builder.Services.TryAddKeyedSingleton(hookName, (serviceProvider, key) =>
- {
- return implementationFactory(serviceProvider);
- });
- }
- else
- {
- builder.Services.TryAddKeyedSingleton(hookName);
- }
-
- return builder;
- }
-
- ///
- /// Add a to allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions
- ///
- /// The instance.
- /// The type to handle.
- /// The handler which reacts to .
- /// The instance.
- public static OpenFeatureBuilder AddHandler(this OpenFeatureBuilder builder, ProviderEventTypes type, EventHandlerDelegate eventHandlerDelegate)
- {
- return AddHandler(builder, type, _ => eventHandlerDelegate);
- }
-
- ///
- /// Add a to allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions
- ///
- /// The instance.
- /// The type to handle.
- /// The handler factory for creating a handler which reacts to .
- /// The instance.
- public static OpenFeatureBuilder AddHandler(this OpenFeatureBuilder builder, ProviderEventTypes type, Func implementationFactory)
- {
- builder.Services.AddSingleton((serviceProvider) =>
- {
- var handler = implementationFactory(serviceProvider);
- return new EventHandlerDelegateWrapper(type, handler);
- });
-
- return builder;
- }
-}
diff --git a/src/OpenFeature.DependencyInjection/OpenFeatureOptions.cs b/src/OpenFeature.DependencyInjection/OpenFeatureOptions.cs
deleted file mode 100644
index e9cc3cb12..000000000
--- a/src/OpenFeature.DependencyInjection/OpenFeatureOptions.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-namespace OpenFeature.DependencyInjection;
-
-///
-/// Options to configure OpenFeature
-///
-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;
-
- internal void AddHookName(string name)
- {
- lock (_hookNames)
- {
- _hookNames.Add(name);
- }
- }
-}
diff --git a/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs b/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs
deleted file mode 100644
index a24c67e78..000000000
--- a/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Options;
-using OpenFeature.DependencyInjection;
-using OpenFeature.DependencyInjection.Internal;
-
-namespace OpenFeature;
-
-///
-/// Contains extension methods for the class.
-///
-public static partial class OpenFeatureServiceCollectionExtensions
-{
- ///
- /// Adds and configures OpenFeature services to the provided .
- ///
- /// The instance.
- /// A configuration action for customizing OpenFeature setup via
- /// The modified instance
- /// Thrown if or is null.
- public static IServiceCollection AddOpenFeature(this IServiceCollection services, Action configure)
- {
- Guard.ThrowIfNull(services);
- Guard.ThrowIfNull(configure);
-
- // Register core OpenFeature services as singletons.
- var api = new Api();
- Api.SetInstance(api);
- services.TryAddSingleton(api);
- services.TryAddSingleton();
-
- var builder = new OpenFeatureBuilder(services);
- configure(builder);
-
- // If a default provider is specified without additional providers,
- // return early as no extra configuration is needed.
- if (builder.HasDefaultProvider && builder.DomainBoundProviderRegistrationCount == 0)
- {
- return services;
- }
-
- // Validate builder configuration to ensure consistency and required setup.
- builder.Validate();
-
- if (!builder.IsPolicyConfigured)
- {
- // Add a default name selector policy to use the first registered provider name as the default.
- builder.AddPolicyName(options =>
- {
- options.DefaultNameSelector = provider =>
- {
- var options = provider.GetRequiredService>().Value;
- return options.ProviderNames.First();
- };
- });
- }
-
- builder.AddPolicyBasedClient();
- return services;
- }
-}
diff --git a/src/OpenFeature.DependencyInjection/PolicyNameOptions.cs b/src/OpenFeature.DependencyInjection/PolicyNameOptions.cs
deleted file mode 100644
index f77b019b1..000000000
--- a/src/OpenFeature.DependencyInjection/PolicyNameOptions.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace OpenFeature.DependencyInjection;
-
-///
-/// Options to configure the default feature client name.
-///
-public class PolicyNameOptions
-{
- ///
- /// A delegate to select the default feature client name.
- ///
- public Func DefaultNameSelector { get; set; } = null!;
-}
diff --git a/src/OpenFeature.DependencyInjection/Providers/Memory/FeatureBuilderExtensions.cs b/src/OpenFeature.DependencyInjection/Providers/Memory/FeatureBuilderExtensions.cs
deleted file mode 100644
index d6346ad78..000000000
--- a/src/OpenFeature.DependencyInjection/Providers/Memory/FeatureBuilderExtensions.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Options;
-using OpenFeature.Providers.Memory;
-
-namespace OpenFeature.DependencyInjection.Providers.Memory;
-
-///
-/// Extension methods for configuring feature providers with .
-///
-#if NET8_0_OR_GREATER
-[System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)]
-#endif
-public static partial class FeatureBuilderExtensions
-{
- ///
- /// Adds an in-memory feature provider to the with a factory for flags.
- ///
- /// 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)
- => builder.AddProvider(provider =>
- {
- var flags = flagsFactory(provider);
- if (flags == null)
- {
- return new InMemoryProvider();
- }
-
- return new InMemoryProvider(flags);
- });
-
- ///
- /// Adds an in-memory feature provider to the with a domain and factory for flags.
- ///
- /// 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)
- => AddInMemoryProvider(builder, domain, (provider, _) => flagsFactory(provider));
-
- ///
- /// 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 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)
- => builder.AddProvider(domain, (provider, key) =>
- {
- var flags = flagsFactory(provider, key);
- if (flags == null)
- {
- return new InMemoryProvider();
- }
-
- return new InMemoryProvider(flags);
- });
-
- ///
- /// Adds an in-memory feature provider to the with optional flag configuration.
- ///
- /// 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)
- => builder.AddProvider(CreateProvider, options => ConfigureFlags(options, configure));
-
- ///
- /// Adds an in-memory feature provider with a specific domain to the with optional flag configuration.
- ///
- /// 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)
- => builder.AddProvider(domain, CreateProvider, options => ConfigureFlags(options, configure));
-
- private static FeatureProvider CreateProvider(IServiceProvider provider, string domain)
- {
- var options = provider.GetRequiredService>().Get(domain);
- if (options.Flags == null)
- {
- return new InMemoryProvider();
- }
-
- return new InMemoryProvider(options.Flags);
- }
-
- private static FeatureProvider CreateProvider(IServiceProvider provider)
- {
- var options = provider.GetRequiredService>().Value;
- if (options.Flags == null)
- {
- return new InMemoryProvider();
- }
-
- return new InMemoryProvider(options.Flags);
- }
-
- private static void ConfigureFlags(InMemoryProviderOptions options, Action>? configure)
- {
- if (configure != null)
- {
- options.Flags = new Dictionary();
- configure.Invoke(options.Flags);
- }
- }
-}
diff --git a/src/OpenFeature.DependencyInjection/Providers/Memory/InMemoryProviderOptions.cs b/src/OpenFeature.DependencyInjection/Providers/Memory/InMemoryProviderOptions.cs
deleted file mode 100644
index ea5433f4e..000000000
--- a/src/OpenFeature.DependencyInjection/Providers/Memory/InMemoryProviderOptions.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using OpenFeature.Providers.Memory;
-
-namespace OpenFeature.DependencyInjection.Providers.Memory;
-
-///
-/// Options for configuring the in-memory feature flag provider.
-///
-public class InMemoryProviderOptions : OpenFeatureOptions
-{
- ///
- /// Gets or sets the feature flags to be used by the in-memory provider.
- ///
- ///
- /// This property allows you to specify a dictionary of flags where the key is the flag name
- /// and the value is the corresponding instance.
- /// If no flags are provided, the in-memory provider will start with an empty set of flags.
- ///
- public IDictionary? Flags { get; set; }
-}
diff --git a/src/OpenFeature.DependencyInjection/README.md b/src/OpenFeature.DependencyInjection/README.md
index 6b9fcfe72..ba9a1e898 100644
--- a/src/OpenFeature.DependencyInjection/README.md
+++ b/src/OpenFeature.DependencyInjection/README.md
@@ -1,6 +1,6 @@
# OpenFeature.DependencyInjection
-> **⚠️ DEPRECATED**: This library is now deprecated. The OpenTelemetry Dependency Injection library has been moved to the OpenFeature Hosting integration in version 2.9.0.
+> **⚠️ DEPRECATED**: This library is now deprecated. The OpenFeature Dependency Injection library has been moved to the OpenFeature Hosting integration in version 2.9.0.
OpenFeature is an open standard for feature flag management, created to support a robust feature flag ecosystem using cloud native technologies. OpenFeature will provide a unified API and SDK, and a developer-first, cloud-native implementation, with extensibility for open source and commercial offerings.
diff --git a/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj b/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj
index 84e5efa61..bf570a897 100644
--- a/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj
+++ b/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj
@@ -1,23 +1,22 @@
-
- netstandard2.0;net8.0;net9.0;net462
- OpenFeature
- README.md
-
+
+ OpenFeature
+ README.md
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs b/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs
index 52c66c42e..d8b52c6cd 100644
--- a/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs
+++ b/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs
@@ -163,47 +163,35 @@ internal static OpenFeatureBuilder AddClient(this OpenFeatureBuilder builder, st
{
if (string.IsNullOrWhiteSpace(name))
{
- if (builder.IsContextConfigured)
+ builder.Services.TryAddScoped(static provider =>
{
- builder.Services.TryAddScoped(static provider =>
+ var api = provider.GetRequiredService();
+ var client = api.GetClient();
+
+ var context = provider.GetService();
+ if (context is not null)
{
- 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();
- });
- }
+ }
+
+ return client;
+ });
}
else
{
- if (builder.IsContextConfigured)
+ builder.Services.TryAddKeyedScoped(name, static (provider, key) =>
{
- builder.Services.TryAddKeyedScoped(name, static (provider, key) =>
+ var api = provider.GetRequiredService();
+ var client = api.GetClient(key!.ToString());
+
+ var context = provider.GetService();
+ if (context is not null)
{
- 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 client;
+ });
}
return builder;
@@ -223,16 +211,25 @@ internal static OpenFeatureBuilder AddPolicyBasedClient(this OpenFeatureBuilder
{
var policy = provider.GetRequiredService>().Value;
var name = policy.DefaultNameSelector(provider);
- if (name == null)
- {
- return provider.GetRequiredService();
- }
- return provider.GetRequiredKeyedService(name);
+ return ResolveFeatureClient(provider, name);
});
return builder;
}
+ private static IFeatureClient ResolveFeatureClient(IServiceProvider provider, string? name = null)
+ {
+ var api = provider.GetRequiredService();
+ var client = api.GetClient(name);
+ var context = provider.GetService();
+ if (context != null)
+ {
+ client.SetContext(context);
+ }
+
+ return client;
+ }
+
///
/// Configures policy name options for OpenFeature using the specified options type.
///
diff --git a/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs b/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs
index 236dc62b0..260b01319 100644
--- a/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs
+++ b/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs
@@ -30,6 +30,7 @@ public static IServiceCollection AddOpenFeature(this IServiceCollection services
var builder = new OpenFeatureBuilder(services);
configure(builder);
+ builder.Services.Configure(c => { }); // Ensures IOptions is available even when no providers are configured.
builder.Services.AddHostedService();
// If a default provider is specified without additional providers,
@@ -50,7 +51,7 @@ public static IServiceCollection AddOpenFeature(this IServiceCollection services
options.DefaultNameSelector = provider =>
{
var options = provider.GetRequiredService>().Value;
- return options.ProviderNames.First();
+ return options.ProviderNames.FirstOrDefault();
};
});
}
diff --git a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
new file mode 100644
index 000000000..12d61c253
--- /dev/null
+++ b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
@@ -0,0 +1,94 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using OpenFeature.Hosting;
+
+namespace OpenFeature.Providers.MultiProvider.DependencyInjection;
+
+///
+/// Extension methods for configuring the multi-provider with .
+///
+public static class FeatureBuilderExtensions
+{
+ ///
+ /// Adds a multi-provider to the with a configuration builder.
+ ///
+ /// The instance to configure.
+ ///
+ /// A delegate to configure the multi-provider using the .
+ ///
+ /// The instance for chaining.
+ public static OpenFeatureBuilder AddMultiProvider(
+ this OpenFeatureBuilder builder,
+ Action configure)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure));
+ }
+
+ return builder.AddProvider(
+ serviceProvider => CreateMultiProviderFromConfigure(serviceProvider, configure));
+ }
+
+ ///
+ /// Adds a multi-provider with a specific domain to the with a configuration builder.
+ ///
+ /// The instance to configure.
+ /// The unique domain of the provider.
+ ///
+ /// A delegate to configure the multi-provider using the .
+ ///
+ /// The instance for chaining.
+ public static OpenFeatureBuilder AddMultiProvider(
+ this OpenFeatureBuilder builder,
+ string domain,
+ Action configure)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (string.IsNullOrWhiteSpace(domain))
+ {
+ throw new ArgumentException("Domain cannot be null or empty.", nameof(domain));
+ }
+
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure), "Configure action cannot be null. Please provide a valid configuration for the multi-provider.");
+ }
+
+ return builder.AddProvider(
+ domain,
+ (serviceProvider, _) => CreateMultiProviderFromConfigure(serviceProvider, configure));
+ }
+
+ private static MultiProvider CreateMultiProviderFromConfigure(IServiceProvider serviceProvider, Action configure)
+ {
+ // Build the multi-provider configuration using the builder
+ var multiProviderBuilder = new MultiProviderBuilder();
+
+ // Apply the configuration action
+ configure(multiProviderBuilder);
+
+ // Build provider entries and strategy from the builder using the service provider
+ var providerEntries = multiProviderBuilder.BuildProviderEntries(serviceProvider);
+ var evaluationStrategy = multiProviderBuilder.BuildEvaluationStrategy(serviceProvider);
+
+ if (providerEntries.Count == 0)
+ {
+ throw new InvalidOperationException("At least one provider must be configured for the multi-provider.");
+ }
+
+ // Get logger from DI
+ var logger = serviceProvider.GetService>();
+
+ return new MultiProvider(providerEntries, evaluationStrategy, logger);
+ }
+}
diff --git a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
new file mode 100644
index 000000000..3353e6122
--- /dev/null
+++ b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
@@ -0,0 +1,135 @@
+using Microsoft.Extensions.DependencyInjection;
+using OpenFeature.Providers.MultiProvider.Models;
+using OpenFeature.Providers.MultiProvider.Strategies;
+
+namespace OpenFeature.Providers.MultiProvider.DependencyInjection;
+
+///
+/// Builder for configuring a multi-provider with dependency injection.
+///
+public class MultiProviderBuilder
+{
+ private readonly List> _providerFactories = [];
+ private Func? _strategyFactory;
+
+ ///
+ /// Adds a provider to the multi-provider configuration using a factory method.
+ ///
+ /// The name for the provider.
+ /// A factory method to create the provider instance.
+ /// The instance for chaining.
+ public MultiProviderBuilder AddProvider(string name, Func factory)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentException("Provider name cannot be null or empty.", nameof(name));
+ }
+
+ if (factory == null)
+ {
+ throw new ArgumentNullException(nameof(factory), "Provider configuration cannot be null.");
+ }
+
+ return AddProvider(name, sp => factory(sp));
+ }
+
+ ///
+ /// Adds a provider to the multi-provider configuration using a type.
+ ///
+ /// The type of the provider to add.
+ /// The name for the provider.
+ /// An optional factory method to create the provider instance. If not provided, the provider will be resolved from the service provider.
+ /// The instance for chaining.
+ public MultiProviderBuilder AddProvider(string name, Func? factory = null)
+ where TProvider : FeatureProvider
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentException("Provider name cannot be null or empty.", nameof(name));
+ }
+
+ this._providerFactories.Add(sp =>
+ {
+ var provider = factory != null
+ ? factory(sp)
+ : sp.GetRequiredService();
+ return new ProviderEntry(provider, name);
+ });
+
+ return this;
+ }
+
+ ///
+ /// Adds a provider instance to the multi-provider configuration.
+ ///
+ /// The name for the provider.
+ /// The provider instance to add.
+ /// The instance for chaining.
+ public MultiProviderBuilder AddProvider(string name, FeatureProvider provider)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentException("Provider name cannot be null or empty.", nameof(name));
+ }
+
+ if (provider == null)
+ {
+ throw new ArgumentNullException(nameof(provider), "Provider configuration cannot be null.");
+ }
+
+ return AddProvider(name, _ => provider);
+ }
+
+ ///
+ /// Sets the evaluation strategy for the multi-provider.
+ ///
+ /// The type of the evaluation strategy.
+ /// The instance for chaining.
+ public MultiProviderBuilder UseStrategy()
+ where TStrategy : BaseEvaluationStrategy, new()
+ {
+ return UseStrategy(static _ => new TStrategy());
+ }
+
+ ///
+ /// Sets the evaluation strategy for the multi-provider using a factory method.
+ ///
+ /// A factory method to create the strategy instance.
+ /// The instance for chaining.
+ public MultiProviderBuilder UseStrategy(Func factory)
+ {
+ this._strategyFactory = factory ?? throw new ArgumentNullException(nameof(factory), "Strategy for multi-provider cannot be null.");
+ return this;
+ }
+
+ ///
+ /// Sets the evaluation strategy for the multi-provider.
+ ///
+ /// The strategy instance to use.
+ /// The instance for chaining.
+ public MultiProviderBuilder UseStrategy(BaseEvaluationStrategy strategy)
+ {
+ if (strategy == null)
+ {
+ throw new ArgumentNullException(nameof(strategy));
+ }
+
+ return UseStrategy(_ => strategy);
+ }
+
+ ///
+ /// Builds the provider entries using the service provider.
+ ///
+ internal List BuildProviderEntries(IServiceProvider serviceProvider)
+ {
+ return this._providerFactories.Select(factory => factory(serviceProvider)).ToList();
+ }
+
+ ///
+ /// Builds the evaluation strategy using the service provider.
+ ///
+ internal BaseEvaluationStrategy? BuildEvaluationStrategy(IServiceProvider serviceProvider)
+ {
+ return this._strategyFactory?.Invoke(serviceProvider);
+ }
+}
diff --git a/src/OpenFeature.Providers.MultiProvider/MultiProvider.cs b/src/OpenFeature.Providers.MultiProvider/MultiProvider.cs
index 9737198f0..e05ce9128 100644
--- a/src/OpenFeature.Providers.MultiProvider/MultiProvider.cs
+++ b/src/OpenFeature.Providers.MultiProvider/MultiProvider.cs
@@ -113,6 +113,43 @@ public override Task> ResolveStringValueAsync(string f
public override Task> ResolveStructureValueAsync(string flagKey, Value defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) =>
this.EvaluateAsync(flagKey, defaultValue, context, cancellationToken);
+ ///
+ public override void Track(string trackingEventName, EvaluationContext? evaluationContext = default, TrackingEventDetails? trackingEventDetails = default)
+ {
+ if (this._disposed == 1)
+ {
+ throw new ObjectDisposedException(nameof(MultiProvider));
+ }
+
+ if (string.IsNullOrWhiteSpace(trackingEventName))
+ {
+ this.LogErrorTrackingEventEmptyName();
+ return;
+ }
+
+ foreach (var registeredProvider in this._registeredProviders)
+ {
+ var providerContext = new StrategyPerProviderContext