name: openfeature description: > USE FOR: Vendor-neutral feature flag evaluation, switching between flag providers (LaunchDarkly, Flagsmith, Split, CloudBees, in-memory) without code changes, standardized targeting and context-based evaluation, and multi-provider setups. DO NOT USE FOR: Simple compile-time feature toggles, runtime compatibility switches (use AppContext), or Microsoft-only ecosystems where Microsoft.FeatureManagement is already established. license: MIT metadata: displayName: "OpenFeature" author: "Tyler-R-Kendrick" version: "1.0.0" compatibility:
- claude
- copilot
- cursor references:
- title: "OpenFeature Official Website" url: "https://openfeature.dev"
- title: "OpenFeature .NET SDK GitHub Repository" url: "https://github.com/open-feature/dotnet-sdk"
- title: "OpenFeature NuGet Package" url: "https://www.nuget.org/packages/OpenFeature"
OpenFeature
Overview
OpenFeature is a CNCF (Cloud Native Computing Foundation) project that provides a vendor-neutral, community-driven API for feature flag evaluation. The .NET SDK (OpenFeature) defines a standard interface for evaluating boolean, string, integer, float, and object flags. Actual flag storage and evaluation logic is provided by a pluggable FeatureProvider -- the SDK ships with an InMemoryProvider for testing, and vendors like LaunchDarkly, Flagsmith, Split, and CloudBees publish their own providers.
The key benefit is portability: application code depends only on the OpenFeature API, and the backing provider can be swapped via configuration without changing business logic. The SDK also supports hooks for logging, metrics, and validation, evaluation context for user targeting, and events for provider readiness and configuration changes.
Install via NuGet: dotnet add package OpenFeature
Basic Setup and Evaluation
using OpenFeature;
using OpenFeature.Model;
using OpenFeature.Providers.Memory;
// Set up the provider (typically done once at startup)
var flagDefinitions = new Dictionary<string, Flag>
{
{ "new-checkout", new Flag<bool>(
variants: new Dictionary<string, bool>
{
{ "on", true },
{ "off", false }
},
defaultVariant: "on") },
{ "banner-text", new Flag<string>(
variants: new Dictionary<string, string>
{
{ "default", "Welcome!" },
{ "holiday", "Happy Holidays!" }
},
defaultVariant: "default") }
};
var provider = new InMemoryProvider(flagDefinitions);
await Api.Instance.SetProviderAsync(provider);
// Get a client and evaluate flags
FeatureClient client = Api.Instance.GetClient();
bool showNewCheckout = await client.GetBooleanValueAsync("new-checkout", false);
string bannerText = await client.GetStringValueAsync("banner-text", "Welcome!");
Evaluation Context and Targeting
EvaluationContext carries user and environment information to the provider for targeted flag evaluation.
using OpenFeature;
using OpenFeature.Model;
public sealed class FeatureFlagService
{
private readonly FeatureClient _client;
public FeatureFlagService()
{
_client = Api.Instance.GetClient("my-service", "1.0.0");
}
public async Task<bool> IsFeatureEnabledAsync(
string flagKey,
string userId,
string? email = null,
string? country = null)
{
var contextBuilder = EvaluationContext.Builder()
.Set("targetingKey", userId)
.Set("email", email ?? string.Empty)
.Set("country", country ?? string.Empty);
EvaluationContext context = contextBuilder.Build();
return await _client.GetBooleanValueAsync(flagKey, false, context);
}
public async Task<FlagEvaluationDetails<bool>> GetFlagDetailsAsync(
string flagKey,
string userId)
{
var context = EvaluationContext.Builder()
.Set("targetingKey", userId)
.Build();
// GetBooleanDetailsAsync returns metadata about the evaluation
return await _client.GetBooleanDetailsAsync(flagKey, false, context);
}
}
ASP.NET Core Integration
Register OpenFeature in the DI container and use it in controllers and middleware.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using OpenFeature;
using OpenFeature.Providers.Memory;
using OpenFeature.Model;
var builder = WebApplication.CreateBuilder(args);
// Register OpenFeature
builder.Services.AddSingleton(Api.Instance);
builder.Services.AddSingleton<FeatureClient>(sp =>
{
return Api.Instance.GetClient("web-api", "1.0.0");
});
var app = builder.Build();
// Set provider before handling requests
var flags = new Dictionary<string, Flag>
{
{ "premium-api", new Flag<bool>(
variants: new Dictionary<string, bool> { { "on", true }, { "off", false } },
defaultVariant: "off") }
};
await Api.Instance.SetProviderAsync(new InMemoryProvider(flags));
app.MapGet("/api/data", async (FeatureClient client, HttpContext httpContext) =>
{
var context = EvaluationContext.Builder()
.Set("targetingKey", httpContext.User.Identity?.Name ?? "anonymous")
.Build();
bool isPremium = await client.GetBooleanValueAsync("premium-api", false, context);
if (isPremium)
{
return Results.Ok(new { Data = "Premium content", Tier = "premium" });
}
return Results.Ok(new { Data = "Standard content", Tier = "standard" });
});
app.Run();
Hooks for Cross-Cutting Concerns
Hooks execute at defined stages of flag evaluation: before, after, error, and finally.
using OpenFeature;
using OpenFeature.Model;
using Microsoft.Extensions.Logging;
public sealed class LoggingHook : Hook
{
private readonly ILogger<LoggingHook> _logger;
public LoggingHook(ILogger<LoggingHook> logger)
{
_logger = logger;
}
public override ValueTask<EvaluationContext> BeforeAsync<T>(
HookContext<T> context,
IReadOnlyDictionary<string, object>? hints = null,
CancellationToken cancellationToken = default)
{
_logger.LogDebug(
"Evaluating flag {FlagKey} of type {Type}",
context.FlagKey,
typeof(T).Name);
return new ValueTask<EvaluationContext>(context.EvaluationContext);
}
public override ValueTask AfterAsync<T>(
HookContext<T> context,
FlagEvaluationDetails<T> details,
IReadOnlyDictionary<string, object>? hints = null,
CancellationToken cancellationToken = default)
{
_logger.LogDebug(
"Flag {FlagKey} evaluated to variant {Variant} with value {Value}",
context.FlagKey,
details.Variant,
details.Value);
return default;
}
public override ValueTask ErrorAsync<T>(
HookContext<T> context,
Exception error,
IReadOnlyDictionary<string, object>? hints = null,
CancellationToken cancellationToken = default)
{
_logger.LogError(error, "Error evaluating flag {FlagKey}", context.FlagKey);
return default;
}
}
// Register hooks
Api.Instance.AddHooks(new LoggingHook(loggerFactory.CreateLogger<LoggingHook>()));
Custom Feature Provider
Implement FeatureProvider to integrate with your own flag storage.
using OpenFeature;
using OpenFeature.Model;
public sealed class DatabaseFeatureProvider : FeatureProvider
{
private readonly string _connectionString;
public DatabaseFeatureProvider(string connectionString)
{
_connectionString = connectionString;
}
public override Metadata GetMetadata()
{
return new Metadata("DatabaseProvider");
}
public override Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(
string flagKey,
bool defaultValue,
EvaluationContext? context = null,
CancellationToken cancellationToken = default)
{
// Query database for the flag value
bool? value = QueryFlagFromDatabase(flagKey, context);
if (value is null)
{
return Task.FromResult(new ResolutionDetails<bool>(
flagKey, defaultValue, reason: "DEFAULT"));
}
return Task.FromResult(new ResolutionDetails<bool>(
flagKey, value.Value, reason: "TARGETING_MATCH", variant: value.Value ? "on" : "off"));
}
public override Task<ResolutionDetails<string>> ResolveStringValueAsync(
string flagKey,
string defaultValue,
EvaluationContext? context = null,
CancellationToken cancellationToken = default)
{
// Implementation for string flags
return Task.FromResult(new ResolutionDetails<string>(
flagKey, defaultValue, reason: "DEFAULT"));
}
public override Task<ResolutionDetails<int>> ResolveIntegerValueAsync(
string flagKey, int defaultValue, EvaluationContext? context = null,
CancellationToken cancellationToken = default) =>
Task.FromResult(new ResolutionDetails<int>(flagKey, defaultValue, reason: "DEFAULT"));
public override Task<ResolutionDetails<double>> ResolveDoubleValueAsync(
string flagKey, double defaultValue, EvaluationContext? context = null,
CancellationToken cancellationToken = default) =>
Task.FromResult(new ResolutionDetails<double>(flagKey, defaultValue, reason: "DEFAULT"));
public override Task<ResolutionDetails<Value>> ResolveStructureValueAsync(
string flagKey, Value defaultValue, EvaluationContext? context = null,
CancellationToken cancellationToken = default) =>
Task.FromResult(new ResolutionDetails<Value>(flagKey, defaultValue, reason: "DEFAULT"));
private bool? QueryFlagFromDatabase(string flagKey, EvaluationContext? context)
{
// Database query implementation
return null;
}
}
OpenFeature vs Microsoft.FeatureManagement
| Aspect | OpenFeature | Microsoft.FeatureManagement |
|---|---|---|
| Governance | CNCF open standard | Microsoft-maintained |
| Provider ecosystem | Multi-vendor (LaunchDarkly, Split, Flagsmith) | Azure App Configuration |
| Flag types | Boolean, string, int, float, object | Boolean with filters |
| Targeting | Via EvaluationContext | Built-in targeting filter |
| Hooks | Before, after, error, finally | N/A |
| ASP.NET integration | Manual or community | Tag helpers, action filters |
| Best for | Multi-cloud, vendor-neutral | Microsoft-centric stacks |
Best Practices
- Set the provider asynchronously at startup using
await Api.Instance.SetProviderAsync(provider)and wait for theProviderReadyevent before serving traffic to avoid evaluating against stale defaults. - Always provide a meaningful default value as the second argument to every evaluation call so the application behaves correctly if the provider is unavailable or the flag is missing.
- Use
EvaluationContextwith atargetingKeyfield set to the user identifier on every evaluation call to enable per-user targeting and consistent percentage-based rollouts. - Register a logging hook globally via
Api.Instance.AddHooksto capture flag evaluation details atDebuglevel for diagnosing targeting issues without modifying business logic. - Use
GetBooleanDetailsAsyncinstead ofGetBooleanValueAsyncwhen you need to log the reason, variant, and metadata alongside the flag value for audit or analytics. - Keep provider initialization separate from flag evaluation; set up the provider in
Program.csand injectFeatureClientinto services rather than accessingApi.Instancedirectly. - Use
InMemoryProviderin unit tests and integration tests to provide deterministic flag values without connecting to a remote service. - Define flag key constants in a shared static class and reference them throughout the codebase to prevent typos and make flag usage searchable.
- Plan for provider migration by coding exclusively against the OpenFeature API; never cast the provider to a vendor-specific type in business logic.
- Monitor the
ProviderErrorandProviderStaleevents to detect connectivity issues with the backing flag service and fall back gracefully to cached or default values.