Sitemap

Multi-Provider Feature Flags in .NET with OpenFeature

Feature flags rarely live in just one place. You may have a vendor service, some environment-based overrides, and a couple of in-memory fallbacks hidden within your app. OpenFeature's MultiProvider lets you compose all of these behind a single, clean client in your .NET applications.​

7 min readJan 8, 2026

--

Press enter or click to view image in full size
OpenFeature Logo
OpenFeature Logo

If you’re interested in standardising and streamlining feature flag management across different tools and platforms, consider exploring the OpenFeature project. OpenFeature is an open specification that provides a vendor-agnostic, community-driven API for feature flagging. It allows you to integrate with your favourite feature flag management tool or in-house solutions without vendor lock-in. It supports many programming languages and makes switching providers or consolidating multiple solutions behind a single interface easy. To learn more about how OpenFeature can enhance your development workflow, visit the OpenFeature website.

In this post, you'll see how to wire up OpenFeature.Providers.MultiProvider with dependency injection, how strategies decide which provider "wins," and a few practical patterns like primary/fallback, migrations, and tenant-specific routing.​

TL;DR

  • OpenFeature.Providers.MultiProvider is a composite FeatureProvider for the OpenFeature .NET SDK.​
  • It allows you to register multiple underlying providers and select a strategy for routing and combining evaluations.​
  • You configure it with:
    -AddMultiProvider(...) when using dependency injection, or
    -new MultiProvider(IEnumerable<ProviderEntry>, IStrategy) when bootstrapping manually.​
  • Your application code continues to use Api.Instance.GetClient(...).GetXxxValueAsync(...) the MultiProvider hides all routing logic.​

What the MultiProvider Actually Is

In OpenFeature, a FeatureProvider abstracts "where flags come from" for a client. A typical provider wraps a single backend, such as a remote flag service, a configuration file, or an in-memory map.​

OpenFeature.Providers.MultiProvider changes that model slightly.​

  • It is itself a FeatureProvider.
  • Internally, it holds a collection of ProviderEntry instances, each wrapping a concrete FeatureProvider plus an optional name.​
  • A strategy (for example, FirstMatchStrategy) decides:
    - Which underlying provider(s) to call for a given flag key.
    - How to combine or short-circuit their results.

Because MultiProvider is still just a FeatureProvider, you can set it as either the default provider or a named "domain" provider in your DI configuration.​

Wiring MultiProvider with Dependency Injection

The recommended way to use MultiProvider in .NET is through the OpenFeature hosting/DI integration. You typically start with something like this:​

using Microsoft.Extensions.DependencyInjection;
using OpenFeature.Hosting;
using OpenFeature.Providers.MultiProvider.DependencyInjection;

// inside Program.cs or equivalent bootstrapping:
var services = new ServiceCollection();
services.AddOpenFeature(featureBuilder =>
{
featureBuilder
.AddMultiProvider("multi-provider", multiProviderBuilder =>
{
// configure providers + strategy here
});
});

A few key points about what happens here:​

  • AddOpenFeature(...) gives you an OpenFeatureBuilder.
  • AddMultiProvider(...) (extension method from FeatureBuilderExtensions):
    - Creates a MultiProviderBuilder.
    - Let's you register multiple inner providers.
    - Registers the constructed MultiProvider as the provider for the given domain (for example, "multi-provider").​

You later consume it like any other OpenFeature client:

// After building the ServiceProvider and starting the host:
var client = sp.GetService<IFeatureClient>();
var value = await client.GetBooleanValueAsync("my-flag", defaultValue: false);

The client is unaware that a MultiProvider backs it; it just sees a standard OpenFeature provider.​

Adding Providers: Factories, Instances, and Types

MultiProviderBuilder gives you several ways to add underlying providers so you can match whatever style your composition root uses.​

Factory-based (DI-friendly) registration

In DI-centric apps (ASP.NET Core, generic host, etc.), a factory-based approach is often the most flexible:

using OpenFeature.Providers.MultiProvider.DependencyInjection;

// Inside AddOpenFeature(...)
featureBuilder.AddMultiProvider("multi-provider", multiProviderBuilder =>
{
multiProviderBuilder
// Provider registered with a simple factory
.AddProvider("primary", sp =>
{
// sp is the IServiceProvider
var flags = new Dictionary<string, bool>
{
{ "my-flag", true }
};
return new InMemoryProvider(flags);
})
.AddProvider("fallback", sp =>
{
// Example of a fictional provider with DI dependencies
var httpClient = sp.GetRequiredService<HttpClient>();
return new MyCompanyFeatureProvider(httpClient);
});
});

Notes:​

  • Each call to .AddProvider(...) registers one underlying FeatureProvider.
  • The name ("primary", "fallback") is mainly for strategies and logging; your application code rarely references it directly.​
  • The factory receives the IServiceProvider, so that you can resolve any dependencies from the container.​

Registering existing instances

If you already have provider instances created in your composition root, you can add them directly:

var primaryProvider = new InMemoryProvider(primaryFlags);
var fallbackProvider = new InMemoryProvider(fallbackFlags);
featureBuilder.AddMultiProvider("multi-provider", multiProviderBuilder =>
{
multiProviderBuilder
.AddProvider("primary", primaryProvider)
.AddProvider("fallback", fallbackProvider);
});

This behaviour is handy when you want explicit construction logic or to reuse a provider across multiple domains.​

Type-based registration

You can also let DI construct the providers, optionally customising them via a factory:

featureBuilder.AddMultiProvider("multi-provider", multiProviderBuilder =>
{
multiProviderBuilder
// Provider resolved from DI
.AddProvider<MyCompanyFeatureProvider>("primary")
// Provider resolved from DI but customized by a factory
.AddProvider<MyCompanyFeatureProvider>("secondary", sp =>
{
var config = sp.GetRequiredService<MyProviderConfig>();
return new MyCompanyFeatureProvider(config);
});
});

In this case, MyCompanyFeatureProvider must itself be registered with the container, for example, with services.AddSingleton<MyCompanyFeatureProvider>();.​

Strategies: How Evaluations Are Combined

Multiple providers alone are not enough; the MultiProvider needs a strategy to decide how to evaluate them and when to stop. Strategies live under OpenFeature.Providers.MultiProvider.Strategies and implement the logic for:​

  • Which provider(s) to try for a given flag key?
  • Whether to short-circuit or continue "flag not found"?
  • How to interpret errors vs. "not found"?​

Configuring a strategy via DI

With DI, you typically chain .UseStrategy<TStrategy>() on the builder:

using OpenFeature.Providers.MultiProvider.Strategies;

featureBuilder.AddMultiProvider("multi-provider", multiProviderBuilder =>
{
multiProviderBuilder
.AddProvider("primary", sp => new InMemoryProvider(primaryFlags))
.AddProvider("secondary", sp => new InMemoryProvider(secondaryFlags))
.UseStrategy<FirstMatchStrategy>(); // default-style strategy
});

FirstMatchStrategy behaves conceptually like this:​

  • Evaluate providers in the registered order.
  • Return the first successful result for the requested flag key.
  • If a provider reports FLAG_NOT_FOUND, move on to the next.
  • If all providers fail or do not have the flag, surface the appropriate error or default.​

This strategy is ideal when you want a "primary provider plus fallback" pattern, where one provider is considered the source of truth, and the others only step in if needed.​

Strategy-driven routing patterns

By swapping or customising strategies, you can implement a few common patterns without changing call sites:​

Override provider

  • One provider holds overrides for specific keys or tenants (for example, environment variables or admin overrides).
  • If it does not handle a flag, the strategy falls back to a base provider.

Migration provider

  • One provider represents the "new" system and another the legacy system.
  • The strategy prefers the new provider but falls back when a flag is missing, so you can move flags gradually.​

Key or tenant-based routing

  • A custom strategy can be routed based on flag key patterns or evaluation context (for example, tenant ID in the context).
  • The logic lives in the Strategies namespace instead of being scattered through business logic.​

The key takeaway is that routing remains within the MultiProvider + strategy, not within your controllers, services, or repositories.​

Manual Setup Without Dependency Injection

If you are not using the hosting/DI integration, you can construct the MultiProvider directly and set it on the Api. The pattern is straightforward:​

using OpenFeature;
using OpenFeature.Providers.InMemory;
using OpenFeature.Providers.MultiProvider;
using OpenFeature.Providers.MultiProvider.Models;
using OpenFeature.Providers.MultiProvider.Strategies;

// Create inner providers
var provider1 = new InMemoryProvider(provider1Flags);
var provider2 = new InMemoryProvider(provider2Flags);

// Wrap them into ProviderEntry instances
var entries = new List<ProviderEntry>
{
new(provider1, "Provider1"),
new(provider2, "Provider2")
};

// Build the MultiProvider
var strategy = new FirstMatchStrategy();
var multiProvider = new MultiProvider(entries, strategy);

// Register it as the default provider
await Api.Instance.SetProviderAsync(multiProvider);

// Use normally
var client = Api.Instance.GetClient();
var value = await client.GetBooleanValueAsync("my-flag", defaultValue: false);

Your client code does not change if you later swap InMemoryProvider for MyCompanyFeatureProvider or update the provider list and strategy.​

Concrete Use Cases

Here are a few scenarios where MultiProvider shines in .NET applications:​

Primary + fallback provider

  • Primary: a remote provider (for example, your company's feature flag service).
  • Fallback: an in-memory provider with safe defaults or local overrides.
  • Strategy: FirstMatchStrategy, so you hit the primary first and only fall back when needed.​

This strategy enables you to maintain a single OpenFeature client while still achieving layered resilience and overrides.

Gradual migration between systems

  • Providers: LegacyFeatureProvider and NewFeatureProvider.
  • Strategy: try the new provider first; if a flag is missing, fall back to the legacy provider.​

You can migrate flags to the new system incrementally, without changing call sites or deploying invasive refactorings.​

Tenant-specific providers with a custom strategy

  • Each tenant may get its own provider instance or configuration.
  • A custom strategy inspects the evaluation context (for example, tenantId) and routes to the right provider entry.​

This behaviour keeps tenant routing logic out of your business code and inside a testable, composable strategy.

Error Handling, Thread Safety, and Lifecycle

MultiProvider is designed to behave like any other provider from the client's perspective, including error handling and lifecycle.​

Error handling

  • MultiProvider aggregates errors from underlying providers according to the chosen strategy.
  • The OpenFeature SDK still returns the usual resolution result; the strategy decides how to interpret partial failures or multiple issues.​

Thread safety

  • MultiProvider is designed for concurrent use in typical .NET scenarios (for example, ASP.NET Core, background services).

Lifecycle

  • As a FeatureProvider, MultiProvider participates in initialisation and shutdown.
  • When you shut down the host or dispose of the provider, underlying providers are disposed of as needed.​

These details mostly "just work," which means you can focus on provider composition and strategy choice rather than on mechanics.

One Client, Many Providers

Once MultiProvider is wired in, your application code keeps calling Api.Instance.GetClient(...).GetXxxValueAsync(...) as usual. Behind that single client, you can combine vendors, environment overrides, in-memory defaults, and tenant-specific logic with a pluggable strategy.​

If you are standardising feature flags across multiple .NET services, MultiProvider is a pragmatic way to grow from "one provider per app" to a more flexible, layered model without rewriting call sites.​

--

--

André Silva
André Silva

Written by André Silva

Senior Software Engineer @ LexisNexis Risk Solutions • Maintainer @ OpenFeature

Responses (1)