From a42ea110822ba1b76856cdda25bb6cf37a66170c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Sat, 1 Nov 2025 16:26:55 +0000
Subject: [PATCH 01/10] feat: Implement multi-provider configuration with
dependency injection support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../FeatureBuilderExtensions.cs | 96 ++++++++++++
.../MultiProviderBuilder.cs | 139 ++++++++++++++++++
.../MultiProviderOptions.cs | 22 +++
...OpenFeature.Providers.MultiProvider.csproj | 25 ++--
4 files changed, 270 insertions(+), 12 deletions(-)
create mode 100644 src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
create mode 100644 src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
create mode 100644 src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderOptions.cs
diff --git a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
new file mode 100644
index 00000000..595403da
--- /dev/null
+++ b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
@@ -0,0 +1,96 @@
+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),
+ null);
+ }
+
+ ///
+ /// 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));
+ }
+
+ return builder.AddProvider(
+ domain,
+ (serviceProvider, _) => CreateMultiProviderFromConfigure(serviceProvider, configure),
+ null);
+ }
+
+ 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 00000000..b586300e
--- /dev/null
+++ b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
@@ -0,0 +1,139 @@
+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));
+ }
+
+ this._providerFactories.Add(sp => new ProviderEntry(factory(sp), name));
+ return this;
+ }
+
+ ///
+ /// 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));
+ }
+
+ this._providerFactories.Add(_ => new ProviderEntry(provider, name));
+ return this;
+ }
+
+ ///
+ /// 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()
+ {
+ this._strategyFactory = _ => new TStrategy();
+ return this;
+ }
+
+ ///
+ /// 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));
+ 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));
+ }
+
+ this._strategyFactory = _ => strategy;
+ return this;
+ }
+
+ ///
+ /// 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/DependencyInjection/MultiProviderOptions.cs b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderOptions.cs
new file mode 100644
index 00000000..5b5a501b
--- /dev/null
+++ b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderOptions.cs
@@ -0,0 +1,22 @@
+using OpenFeature.Hosting;
+using OpenFeature.Providers.MultiProvider.Models;
+using OpenFeature.Providers.MultiProvider.Strategies;
+
+namespace OpenFeature.Providers.MultiProvider.DependencyInjection;
+
+///
+/// Options for configuring the multi-provider.
+///
+public class MultiProviderOptions : OpenFeatureOptions
+{
+ ///
+ /// Gets or sets the list of provider entries for the multi-provider.
+ ///
+ public List ProviderEntries { get; set; } = [];
+
+ ///
+ /// Gets or sets the evaluation strategy to use for the multi-provider.
+ /// If not set, the FirstMatchStrategy will be used by default.
+ ///
+ public BaseEvaluationStrategy? EvaluationStrategy { get; set; }
+}
diff --git a/src/OpenFeature.Providers.MultiProvider/OpenFeature.Providers.MultiProvider.csproj b/src/OpenFeature.Providers.MultiProvider/OpenFeature.Providers.MultiProvider.csproj
index d999c561..99d30b4a 100644
--- a/src/OpenFeature.Providers.MultiProvider/OpenFeature.Providers.MultiProvider.csproj
+++ b/src/OpenFeature.Providers.MultiProvider/OpenFeature.Providers.MultiProvider.csproj
@@ -1,17 +1,18 @@
-
- OpenFeature.Providers.MultiProvider
- README.md
-
+
+ OpenFeature.Providers.MultiProvider
+ README.md
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
\ No newline at end of file
From 148484ebf15b2b3c6c6fa2353594e12098735d47 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Sat, 1 Nov 2025 17:08:50 +0000
Subject: [PATCH 02/10] feat: Add multi-provider support with dependency
injection and flag evaluation endpoints
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
samples/AspNetCore/Program.cs | 48 ++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/samples/AspNetCore/Program.cs b/samples/AspNetCore/Program.cs
index 3dc0203b..c8c35e0c 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;
@@ -59,9 +60,33 @@
{ "disable", new Value(Structure.Builder().Set(nameof(TestConfig.Threshold), 0).Build()) }
}, "disable")
}
- });
+ })
+ .AddMultiProvider("multi-provider", multiProviderBuilder => CreateMultiProviderBuilder(multiProviderBuilder))
+ .AddPolicyName(policy => policy.DefaultNameSelector = provider => "InMemory");
});
+static void CreateMultiProviderBuilder(MultiProviderBuilder multiProviderBuilder)
+{
+ // Create first in-memory provider with some 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 provider1 = new InMemoryProvider(provider1Flags);
+
+ // Create second in-memory provider with different flags
+ var provider2Flags = new Dictionary
+ {
+ { "providername", new Flag(new Dictionary { { "enabled", "enabled-provider2" }, { "disabled", "disabled-provider2" } }, "enabled") },
+ };
+ var provider2 = new InMemoryProvider(provider2Flags);
+
+ multiProviderBuilder.AddProvider("p1", provider1)
+ .AddProvider("p2", provider2)
+ .UseStrategy();
+}
+
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -139,6 +164,27 @@
}
});
+app.MapGet("/multi-provider-di", async ([FromServices] Api openFeatureApi) =>
+{
+ try
+ {
+ var featureClient = openFeatureApi.GetClient("multi-provider");
+
+ // 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)
+ {
+ return Results.InternalServerError();
+ }
+});
+
app.Run();
From 411a491d62e747561cd147c8e8f36e99c3e7138a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Sat, 1 Nov 2025 17:32:37 +0000
Subject: [PATCH 03/10] refactor: Simplify AddMultiProvider method by removing
redundant parameters
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
samples/AspNetCore/Program.cs | 47 +++++++++----------
.../FeatureBuilderExtensions.cs | 10 ++--
2 files changed, 26 insertions(+), 31 deletions(-)
diff --git a/samples/AspNetCore/Program.cs b/samples/AspNetCore/Program.cs
index c8c35e0c..67eb5243 100644
--- a/samples/AspNetCore/Program.cs
+++ b/samples/AspNetCore/Program.cs
@@ -61,32 +61,29 @@
}, "disable")
}
})
- .AddMultiProvider("multi-provider", multiProviderBuilder => CreateMultiProviderBuilder(multiProviderBuilder))
+ .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");
});
-static void CreateMultiProviderBuilder(MultiProviderBuilder multiProviderBuilder)
-{
- // Create first in-memory provider with some 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 provider1 = new InMemoryProvider(provider1Flags);
-
- // Create second in-memory provider with different flags
- var provider2Flags = new Dictionary
- {
- { "providername", new Flag(new Dictionary { { "enabled", "enabled-provider2" }, { "disabled", "disabled-provider2" } }, "enabled") },
- };
- var provider2 = new InMemoryProvider(provider2Flags);
-
- multiProviderBuilder.AddProvider("p1", provider1)
- .AddProvider("p2", provider2)
- .UseStrategy();
-}
-
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -179,9 +176,9 @@ static void CreateMultiProviderBuilder(MultiProviderBuilder multiProviderBuilder
return Results.Ok();
}
- catch (Exception)
+ catch (Exception ex)
{
- return Results.InternalServerError();
+ return Results.Problem($"Error: {ex.Message}\n\nStack: {ex.StackTrace}");
}
});
diff --git a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
index 595403da..5f2f9ad8 100644
--- a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
+++ b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
@@ -31,9 +31,8 @@ public static OpenFeatureBuilder AddMultiProvider(
throw new ArgumentNullException(nameof(configure));
}
- return builder.AddProvider(
- serviceProvider => CreateMultiProviderFromConfigure(serviceProvider, configure),
- null);
+ return builder.AddProvider(
+ serviceProvider => CreateMultiProviderFromConfigure(serviceProvider, configure));
}
///
@@ -65,10 +64,9 @@ public static OpenFeatureBuilder AddMultiProvider(
throw new ArgumentNullException(nameof(configure));
}
- return builder.AddProvider(
+ return builder.AddProvider(
domain,
- (serviceProvider, _) => CreateMultiProviderFromConfigure(serviceProvider, configure),
- null);
+ (serviceProvider, _) => CreateMultiProviderFromConfigure(serviceProvider, configure));
}
private static MultiProvider CreateMultiProviderFromConfigure(IServiceProvider serviceProvider, Action configure)
From 49102d09ee6cb25f88595ed6a5501698e51bbc8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Sat, 1 Nov 2025 19:07:14 +0000
Subject: [PATCH 04/10] test: Add unit tests for MultiProviderBuilder and
MultiProviderDependencyInjection
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../MultiProviderBuilderTests.cs | 327 ++++++++++++++++++
.../MultiProviderDependencyInjectionTests.cs | 292 ++++++++++++++++
2 files changed, 619 insertions(+)
create mode 100644 test/OpenFeature.Providers.MultiProvider.Tests/DependencyInjection/MultiProviderBuilderTests.cs
create mode 100644 test/OpenFeature.Providers.MultiProvider.Tests/DependencyInjection/MultiProviderDependencyInjectionTests.cs
diff --git a/test/OpenFeature.Providers.MultiProvider.Tests/DependencyInjection/MultiProviderBuilderTests.cs b/test/OpenFeature.Providers.MultiProvider.Tests/DependencyInjection/MultiProviderBuilderTests.cs
new file mode 100644
index 00000000..40fbb6b1
--- /dev/null
+++ b/test/OpenFeature.Providers.MultiProvider.Tests/DependencyInjection/MultiProviderBuilderTests.cs
@@ -0,0 +1,327 @@
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using OpenFeature.Model;
+using OpenFeature.Providers.MultiProvider.DependencyInjection;
+using OpenFeature.Providers.MultiProvider.Strategies;
+using OpenFeature.Providers.MultiProvider.Tests.Utils;
+
+namespace OpenFeature.Providers.MultiProvider.Tests.DependencyInjection;
+
+public class MultiProviderBuilderTests
+{
+ private readonly FeatureProvider _mockProvider = Substitute.For();
+
+ public MultiProviderBuilderTests()
+ {
+ _mockProvider.GetMetadata().Returns(new Metadata("mock-provider"));
+ }
+
+ [Fact]
+ public void AddProvider_WithNullName_ThrowsArgumentException()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddProvider(null!, _mockProvider));
+ }
+
+ [Fact]
+ public void AddProvider_WithEmptyName_ThrowsArgumentException()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddProvider("", _mockProvider));
+ }
+
+ [Fact]
+ public void AddProvider_WithNullProvider_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddProvider("test", (FeatureProvider)null!));
+ }
+
+ [Fact]
+ public void AddProvider_WithFactory_WithNullName_ThrowsArgumentException()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddProvider(null!, sp => _mockProvider));
+ }
+
+ [Fact]
+ public void AddProvider_WithFactory_WithNullFactory_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddProvider("test", (Func)null!));
+ }
+
+ [Fact]
+ public void AddProvider_Generic_WithNullName_ThrowsArgumentException()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddProvider(null!));
+ }
+
+ [Fact]
+ public void AddProvider_Generic_WithEmptyName_ThrowsArgumentException()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddProvider(""));
+ }
+
+ [Fact]
+ public void AddProvider_AddsProviderToBuilder()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ builder.AddProvider("test-provider", _mockProvider);
+ var entries = builder.BuildProviderEntries(serviceProvider);
+
+ // Assert
+ Assert.Single(entries);
+ Assert.Equal("test-provider", entries[0].Name);
+ Assert.Same(_mockProvider, entries[0].Provider);
+ }
+
+ [Fact]
+ public void AddProvider_WithFactory_AddsProviderToBuilder()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ builder.AddProvider("test-provider", sp => _mockProvider);
+ var entries = builder.BuildProviderEntries(serviceProvider);
+
+ // Assert
+ Assert.Single(entries);
+ Assert.Equal("test-provider", entries[0].Name);
+ Assert.Same(_mockProvider, entries[0].Provider);
+ }
+
+ [Fact]
+ public void AddProvider_Generic_WithFactory_AddsProviderToBuilder()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ builder.AddProvider("test-provider", sp => new TestProvider("test-provider"));
+ var entries = builder.BuildProviderEntries(serviceProvider);
+
+ // Assert
+ Assert.Single(entries);
+ Assert.Equal("test-provider", entries[0].Name);
+ Assert.IsType(entries[0].Provider);
+ }
+
+ [Fact]
+ public void AddProvider_Generic_WithoutFactory_ResolvesFromServiceProvider()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ services.AddTransient(_ => new TestProvider("test-provider"));
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ builder.AddProvider("test-provider");
+ var entries = builder.BuildProviderEntries(serviceProvider);
+
+ // Assert
+ Assert.Single(entries);
+ Assert.Equal("test-provider", entries[0].Name);
+ Assert.IsType(entries[0].Provider);
+ }
+
+ [Fact]
+ public void AddProvider_MultipleProviders_AddsAllProviders()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ var provider1 = Substitute.For();
+ var provider2 = Substitute.For();
+ var provider3 = Substitute.For();
+
+ provider1.GetMetadata().Returns(new Metadata("provider1"));
+ provider2.GetMetadata().Returns(new Metadata("provider2"));
+ provider3.GetMetadata().Returns(new Metadata("provider3"));
+
+ // Act
+ builder
+ .AddProvider("provider1", provider1)
+ .AddProvider("provider2", sp => provider2)
+ .AddProvider("provider3", sp => new TestProvider("provider3"));
+
+ var entries = builder.BuildProviderEntries(serviceProvider);
+
+ // Assert
+ Assert.Equal(3, entries.Count);
+ Assert.Equal("provider1", entries[0].Name);
+ Assert.Equal("provider2", entries[1].Name);
+ Assert.Equal("provider3", entries[2].Name);
+ }
+
+ [Fact]
+ public void UseStrategy_Generic_SetsStrategy()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ builder.UseStrategy();
+ var strategy = builder.BuildEvaluationStrategy(serviceProvider);
+
+ // Assert
+ Assert.NotNull(strategy);
+ Assert.IsType(strategy);
+ }
+
+ [Fact]
+ public void UseStrategy_WithInstance_SetsStrategy()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+ var strategyInstance = new FirstMatchStrategy();
+
+ // Act
+ builder.UseStrategy(strategyInstance);
+ var strategy = builder.BuildEvaluationStrategy(serviceProvider);
+
+ // Assert
+ Assert.NotNull(strategy);
+ Assert.Same(strategyInstance, strategy);
+ }
+
+ [Fact]
+ public void UseStrategy_WithNullInstance_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.UseStrategy((FirstMatchStrategy)null!));
+ }
+
+ [Fact]
+ public void UseStrategy_WithFactory_SetsStrategy()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ builder.UseStrategy(sp => new FirstMatchStrategy());
+ var strategy = builder.BuildEvaluationStrategy(serviceProvider);
+
+ // Assert
+ Assert.NotNull(strategy);
+ Assert.IsType(strategy);
+ }
+
+ [Fact]
+ public void UseStrategy_WithNullFactory_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.UseStrategy((Func)null!));
+ }
+
+ [Fact]
+ public void BuildEvaluationStrategy_WithNoStrategy_ReturnsNull()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var strategy = builder.BuildEvaluationStrategy(serviceProvider);
+
+ // Assert
+ Assert.Null(strategy);
+ }
+
+ [Fact]
+ public void BuildProviderEntries_WithNoProviders_ReturnsEmptyList()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var entries = builder.BuildProviderEntries(serviceProvider);
+
+ // Assert
+ Assert.NotNull(entries);
+ Assert.Empty(entries);
+ }
+
+ [Fact]
+ public void Builder_ChainsMethodsCorrectly()
+ {
+ // Arrange
+ var builder = new MultiProviderBuilder();
+ var services = new ServiceCollection();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var result = builder
+ .AddProvider("provider1", _mockProvider)
+ .AddProvider("provider2", sp => _mockProvider)
+ .UseStrategy();
+
+ var entries = builder.BuildProviderEntries(serviceProvider);
+ var strategy = builder.BuildEvaluationStrategy(serviceProvider);
+
+ // Assert
+ Assert.Same(builder, result);
+ Assert.Equal(2, entries.Count);
+ Assert.NotNull(strategy);
+ }
+}
diff --git a/test/OpenFeature.Providers.MultiProvider.Tests/DependencyInjection/MultiProviderDependencyInjectionTests.cs b/test/OpenFeature.Providers.MultiProvider.Tests/DependencyInjection/MultiProviderDependencyInjectionTests.cs
new file mode 100644
index 00000000..5d2b6dbc
--- /dev/null
+++ b/test/OpenFeature.Providers.MultiProvider.Tests/DependencyInjection/MultiProviderDependencyInjectionTests.cs
@@ -0,0 +1,292 @@
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using OpenFeature.Hosting;
+using OpenFeature.Model;
+using OpenFeature.Providers.MultiProvider.DependencyInjection;
+using OpenFeature.Providers.MultiProvider.Strategies;
+using OpenFeature.Providers.MultiProvider.Tests.Utils;
+
+namespace OpenFeature.Providers.MultiProvider.Tests.DependencyInjection;
+
+public class MultiProviderDependencyInjectionTests
+{
+ private readonly FeatureProvider _mockProvider = Substitute.For();
+
+ public MultiProviderDependencyInjectionTests()
+ {
+ _mockProvider.GetMetadata().Returns(new Metadata("test-provider"));
+ _mockProvider.ResolveBooleanValueAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ .Returns(Task.FromResult(new ResolutionDetails("test", true)));
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithNullBuilder_ThrowsArgumentNullException()
+ {
+ // Arrange
+ OpenFeatureBuilder builder = null!;
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddMultiProvider(b => b.AddProvider("test", _mockProvider)));
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithNullConfigure_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var builder = new OpenFeatureBuilder(services);
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddMultiProvider(null!));
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithDomain_WithNullBuilder_ThrowsArgumentNullException()
+ {
+ // Arrange
+ OpenFeatureBuilder builder = null!;
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddMultiProvider("test-domain", b => b.AddProvider("test", _mockProvider)));
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithDomain_WithNullDomain_ThrowsArgumentException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var builder = new OpenFeatureBuilder(services);
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddMultiProvider(null!, b => b.AddProvider("test", _mockProvider)));
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithDomain_WithEmptyDomain_ThrowsArgumentException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var builder = new OpenFeatureBuilder(services);
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddMultiProvider("", b => b.AddProvider("test", _mockProvider)));
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithDomain_WithNullConfigure_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var builder = new OpenFeatureBuilder(services);
+
+ // Act & Assert
+ Assert.Throws(() =>
+ builder.AddMultiProvider("test-domain", null!));
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithNoProviders_ThrowsInvalidOperationException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOpenFeature(builder =>
+ {
+ builder.AddMultiProvider(b => { }); // Empty configuration
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ serviceProvider.GetRequiredService());
+ }
+
+ [Fact]
+ public void AddMultiProvider_RegistersProviderCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOpenFeature(builder =>
+ {
+ builder.AddMultiProvider(b =>
+ {
+ b.AddProvider("provider1", _mockProvider);
+ });
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var provider = serviceProvider.GetRequiredService();
+
+ // Assert
+ Assert.NotNull(provider);
+ Assert.IsType(provider);
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithDomain_RegistersProviderCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOpenFeature(builder =>
+ {
+ builder.AddMultiProvider("test-domain", b =>
+ {
+ b.AddProvider("provider1", _mockProvider);
+ });
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var provider = serviceProvider.GetRequiredKeyedService("test-domain");
+
+ // Assert
+ Assert.NotNull(provider);
+ Assert.IsType(provider);
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithMultipleProviders_CreatesMultiProvider()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var provider1 = Substitute.For();
+ var provider2 = Substitute.For();
+ var provider3 = Substitute.For();
+
+ provider1.GetMetadata().Returns(new Metadata("provider1"));
+ provider2.GetMetadata().Returns(new Metadata("provider2"));
+ provider3.GetMetadata().Returns(new Metadata("provider3"));
+
+ services.AddOpenFeature(builder =>
+ {
+ builder.AddMultiProvider(b =>
+ {
+ b.AddProvider("provider1", provider1)
+ .AddProvider("provider2", provider2)
+ .AddProvider("provider3", provider3);
+ });
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var provider = serviceProvider.GetRequiredService();
+
+ // Assert
+ Assert.NotNull(provider);
+ Assert.IsType(provider);
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithStrategy_CreatesMultiProviderWithStrategy()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOpenFeature(builder =>
+ {
+ builder.AddMultiProvider(b =>
+ {
+ b.AddProvider("provider1", _mockProvider)
+ .UseStrategy();
+ });
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var provider = serviceProvider.GetRequiredService();
+
+ // Assert
+ Assert.NotNull(provider);
+ Assert.IsType(provider);
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithFactoryProvider_CreatesProviderFromFactory()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var factoryCalled = false;
+
+ services.AddOpenFeature(builder =>
+ {
+ builder.AddMultiProvider(b =>
+ {
+ b.AddProvider("provider1", sp =>
+ {
+ factoryCalled = true;
+ var mockProvider = Substitute.For();
+ mockProvider.GetMetadata().Returns(new Metadata("factory-provider"));
+ return mockProvider;
+ });
+ });
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var provider = serviceProvider.GetRequiredService();
+
+ // Assert
+ Assert.NotNull(provider);
+ Assert.True(factoryCalled, "Factory method should have been called");
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithTypedProvider_ResolvesFromServiceProvider()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddTransient(_ => new TestProvider("test-provider"));
+
+ services.AddOpenFeature(builder =>
+ {
+ builder.AddMultiProvider(b =>
+ {
+ b.AddProvider("provider1");
+ });
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var provider = serviceProvider.GetRequiredService();
+
+ // Assert
+ Assert.NotNull(provider);
+ Assert.IsType(provider);
+ }
+
+ [Fact]
+ public void AddMultiProvider_WithLogger_CreatesMultiProvider()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddLogging();
+
+ services.AddOpenFeature(builder =>
+ {
+ builder.AddMultiProvider(b =>
+ {
+ b.AddProvider("provider1", _mockProvider);
+ });
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Act
+ var provider = serviceProvider.GetRequiredService();
+
+ // Assert
+ Assert.NotNull(provider);
+ Assert.IsType(provider);
+ }
+}
From 73679e1a054d9fa18c3d6da917b5868f93376ce9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Sat, 1 Nov 2025 19:10:36 +0000
Subject: [PATCH 05/10] fix: Update project reference to use
OpenFeature.Hosting instead of OpenFeature.DependencyInjection
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../OpenFeature.AotCompatibility.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj b/test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj
index d416bd75..823d96f0 100644
--- a/test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj
+++ b/test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj
@@ -18,7 +18,7 @@
-
+
From 4e3bcbb404dcfe3dd0f49edd866e7843019ba55e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Sat, 1 Nov 2025 19:11:45 +0000
Subject: [PATCH 06/10] refactor: Remove MultiProviderOptions class as part of
code cleanup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../MultiProviderOptions.cs | 22 -------------------
1 file changed, 22 deletions(-)
delete mode 100644 src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderOptions.cs
diff --git a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderOptions.cs b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderOptions.cs
deleted file mode 100644
index 5b5a501b..00000000
--- a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderOptions.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using OpenFeature.Hosting;
-using OpenFeature.Providers.MultiProvider.Models;
-using OpenFeature.Providers.MultiProvider.Strategies;
-
-namespace OpenFeature.Providers.MultiProvider.DependencyInjection;
-
-///
-/// Options for configuring the multi-provider.
-///
-public class MultiProviderOptions : OpenFeatureOptions
-{
- ///
- /// Gets or sets the list of provider entries for the multi-provider.
- ///
- public List ProviderEntries { get; set; } = [];
-
- ///
- /// Gets or sets the evaluation strategy to use for the multi-provider.
- /// If not set, the FirstMatchStrategy will be used by default.
- ///
- public BaseEvaluationStrategy? EvaluationStrategy { get; set; }
-}
From 3d068d0629d3de26a8406b61e091b6240cf375e2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Sun, 9 Nov 2025 14:31:11 +0000
Subject: [PATCH 07/10] docs: add dependency injection documentation to
MultiProvider README
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Document AddMultiProvider extension methods
- Explain MultiProviderBuilder usage patterns
- Show examples for adding providers via factory, instance, and DI
- Document evaluation strategy configuration
- Add domain-scoped provider configuration examples
- Position DI setup as recommended approach
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../README.md | 83 +++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/src/OpenFeature.Providers.MultiProvider/README.md b/src/OpenFeature.Providers.MultiProvider/README.md
index 8b12807c..00108c0a 100644
--- a/src/OpenFeature.Providers.MultiProvider/README.md
+++ b/src/OpenFeature.Providers.MultiProvider/README.md
@@ -18,8 +18,91 @@ dotnet add package OpenFeature.Providers.MultiProvider
## Usage
+### Dependency Injection Setup (Recommended)
+
+The MultiProvider integrates seamlessly with the OpenFeature dependency injection system, allowing you to configure multiple providers using the `AddMultiProvider` extension method:
+
+```csharp
+using OpenFeature.Providers.MultiProvider.DependencyInjection;
+
+builder.Services.AddOpenFeature(featureBuilder =>
+{
+ featureBuilder
+ .AddMultiProvider("multi-provider", multiProviderBuilder =>
+ {
+ // Add providers using factory methods for proper DI integration
+ multiProviderBuilder
+ .AddProvider("primary", sp => new YourPrimaryProvider())
+ .AddProvider("fallback", sp => new YourFallbackProvider())
+ .UseStrategy();
+ });
+});
+
+// Retrieve and use the client
+var featureClient = openFeatureApi.GetClient("multi-provider");
+var result = await featureClient.GetBooleanValueAsync("my-flag", false);
+```
+
+#### Adding Providers with DI
+
+The `MultiProviderBuilder` provides several methods to add providers:
+
+**Using Factory Methods:**
+```csharp
+multiProviderBuilder
+ .AddProvider("provider-name", sp => new InMemoryProvider(flags))
+ .AddProvider("another-provider", sp => sp.GetRequiredService());
+```
+
+**Using Provider Instances:**
+```csharp
+var provider = new InMemoryProvider(flags);
+multiProviderBuilder.AddProvider("provider-name", provider);
+```
+
+**Using Generic Type Resolution:**
+```csharp
+// Provider will be resolved from DI container
+multiProviderBuilder.AddProvider("provider-name");
+
+// Or with custom factory
+multiProviderBuilder.AddProvider("provider-name", sp => new YourProvider(config));
+```
+
+#### Configuring Evaluation Strategy
+
+Specify an evaluation strategy using any of these methods:
+
+```csharp
+// Using generic type
+multiProviderBuilder.UseStrategy();
+
+// Using factory method with DI
+multiProviderBuilder.UseStrategy(sp => new FirstMatchStrategy());
+
+// Using strategy instance
+multiProviderBuilder.UseStrategy(new ComparisonStrategy());
+```
+
+#### Using with Named Domains
+
+Configure the MultiProvider for a specific domain:
+
+```csharp
+featureBuilder
+ .AddMultiProvider("production-domain", multiProviderBuilder =>
+ {
+ multiProviderBuilder
+ .AddProvider("remote", sp => new RemoteProvider())
+ .AddProvider("cache", sp => new CacheProvider())
+ .UseStrategy();
+ });
+```
+
### Basic Setup
+For scenarios where dependency injection is not available, you can use the traditional setup:
+
```csharp
using OpenFeature;
using OpenFeature.Providers.MultiProvider;
From 1b2ef7cffdbb5ab70751d228f7c2c096bde84387 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Mon, 10 Nov 2025 22:03:43 +0000
Subject: [PATCH 08/10] Apply suggestions from code review
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Kyle <38759683+kylejuliandev@users.noreply.github.com>
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
samples/AspNetCore/Program.cs | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/samples/AspNetCore/Program.cs b/samples/AspNetCore/Program.cs
index 67eb5243..6651e4fb 100644
--- a/samples/AspNetCore/Program.cs
+++ b/samples/AspNetCore/Program.cs
@@ -161,12 +161,10 @@
}
});
-app.MapGet("/multi-provider-di", async ([FromServices] Api openFeatureApi) =>
+app.MapGet("/multi-provider-di", async ([FromKeyedServices("multi-provider")] IFeatureClient featureClient) =>
{
try
{
- var featureClient = openFeatureApi.GetClient("multi-provider");
-
// Test flag evaluation from different providers
var maxItemsFlag = await featureClient.GetIntegerDetailsAsync("max-items", 0);
var providerNameFlag = await featureClient.GetStringDetailsAsync("providername", "default");
From 3c1eb53a345f7c0881ae00d3ee2f70b44d3f7a8b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Tue, 11 Nov 2025 10:17:59 +0000
Subject: [PATCH 09/10] Apply suggestions from code review
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Kyle <38759683+kylejuliandev@users.noreply.github.com>
Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com>
---
.../DependencyInjection/MultiProviderBuilder.cs | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
index b586300e..08e48677 100644
--- a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
+++ b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
@@ -30,8 +30,7 @@ public MultiProviderBuilder AddProvider(string name, Func new ProviderEntry(factory(sp), name));
- return this;
+ return AddProvider(name, sp => factory(sp));
}
///
@@ -78,8 +77,7 @@ public MultiProviderBuilder AddProvider(string name, FeatureProvider provider)
throw new ArgumentNullException(nameof(provider));
}
- this._providerFactories.Add(_ => new ProviderEntry(provider, name));
- return this;
+ return AddProvider(name, _ => provider);
}
///
@@ -90,8 +88,7 @@ public MultiProviderBuilder AddProvider(string name, FeatureProvider provider)
public MultiProviderBuilder UseStrategy()
where TStrategy : BaseEvaluationStrategy, new()
{
- this._strategyFactory = _ => new TStrategy();
- return this;
+ return UseStrategy(static _ => new TStrategy());
}
///
@@ -117,8 +114,7 @@ public MultiProviderBuilder UseStrategy(BaseEvaluationStrategy strategy)
throw new ArgumentNullException(nameof(strategy));
}
- this._strategyFactory = _ => strategy;
- return this;
+ return UseStrategy(_ => strategy);
}
///
From 2656177bb33ac85e1e420fc543c0f8afb1c4282b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?=
<2493377+askpt@users.noreply.github.com>
Date: Tue, 11 Nov 2025 10:26:34 +0000
Subject: [PATCH 10/10] fix: Improve null argument exception messages in
MultiProvider configuration
Signed-off-by: GitHub
---
.../DependencyInjection/FeatureBuilderExtensions.cs | 2 +-
.../DependencyInjection/MultiProviderBuilder.cs | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
index 5f2f9ad8..12d61c25 100644
--- a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
+++ b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/FeatureBuilderExtensions.cs
@@ -61,7 +61,7 @@ public static OpenFeatureBuilder AddMultiProvider(
if (configure == null)
{
- throw new ArgumentNullException(nameof(configure));
+ throw new ArgumentNullException(nameof(configure), "Configure action cannot be null. Please provide a valid configuration for the multi-provider.");
}
return builder.AddProvider(
diff --git a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
index 08e48677..3353e612 100644
--- a/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
+++ b/src/OpenFeature.Providers.MultiProvider/DependencyInjection/MultiProviderBuilder.cs
@@ -27,7 +27,7 @@ public MultiProviderBuilder AddProvider(string name, Func(name, sp => factory(sp));
@@ -74,7 +74,7 @@ public MultiProviderBuilder AddProvider(string name, FeatureProvider provider)
if (provider == null)
{
- throw new ArgumentNullException(nameof(provider));
+ throw new ArgumentNullException(nameof(provider), "Provider configuration cannot be null.");
}
return AddProvider(name, _ => provider);
@@ -98,7 +98,7 @@ public MultiProviderBuilder UseStrategy()
/// The instance for chaining.
public MultiProviderBuilder UseStrategy(Func factory)
{
- this._strategyFactory = factory ?? throw new ArgumentNullException(nameof(factory));
+ this._strategyFactory = factory ?? throw new ArgumentNullException(nameof(factory), "Strategy for multi-provider cannot be null.");
return this;
}