C# SDK Reference
TL;DR: Install
Featureflip.Client, callclient.BoolVariation("flag-key", context, false), and you’re evaluating flags — synchronous, noawaitin your hot path.
The Featureflip.Client NuGet package provides a thread-safe client for evaluating feature flags in .NET applications. It multi-targets .NET 8.0, .NET 10.0, and .NET Standard 2.0, so the same package works across modern .NET, .NET Framework 4.6.1+, and any other runtime that supports .NET Standard 2.0. Real-time updates arrive over SSE streaming, with a polling fallback when streaming is unavailable.
The SDK is designed as a process-wide singleton. FeatureflipClient.Get(sdkKey) returns handles backed by a shared, refcounted client, so you can resolve it from anywhere — including misregistered DI scopes — without opening duplicate streaming connections. The companion Featureflip.Client.Extensions.DependencyInjection package wires this up correctly for ASP.NET Core with services.AddFeatureflip(...), and the same singleton survives the entire application lifetime.
Variation methods (BoolVariation, StringVariation, IntVariation, DoubleVariation, JsonVariation<T>) read from a ConcurrentDictionary and return synchronously — no await required in your controller actions or hot paths. Beyond Microsoft.Extensions.Logging.Abstractions and System.Text.Json, the SDK has no direct dependencies, keeping the deployed footprint small.
Lifetime
Section titled “Lifetime”The Featureflip C# SDK is designed to be used as a singleton across your application. Obtain the client via the static factory:
var client = FeatureflipClient.Get("your-sdk-key");Calling Get() multiple times with the same SDK key returns handles that share a single underlying client — the SDK maintains a process-wide cache keyed by SDK key. You cannot accidentally open duplicate streaming connections by constructing multiple clients.
If you’re using Microsoft.Extensions.DependencyInjection, the AddFeatureflip extension method registers the client correctly as a singleton:
services.AddFeatureflip("your-sdk-key");Even if you hand-roll a scoped or transient DI registration, the factory’s deduplication ensures all resolves share one underlying client — the misregistration is neutralized, not catastrophic.
Disposal
Section titled “Disposal”Dispose() is refcounted: when multiple handles share one cached client, disposing one handle does not shut down the shared background tasks. The real shutdown runs only when the last handle is disposed. In a typical application this means the client lives for the entire process lifetime and is cleaned up automatically at shutdown.
Requirements
Section titled “Requirements”The SDK multi-targets the following frameworks:
| Target | Minimum Version |
|---|---|
| .NET | 8.0 |
| .NET | 10.0 |
| .NET Standard | 2.0 (.NET Framework 4.6.1+, .NET Core 2.0+) |
Installation
Section titled “Installation”dotnet add package Featureflip.ClientFor ASP.NET Core dependency injection support, also install the extensions package:
dotnet add package Featureflip.Client.Extensions.DependencyInjectionConfiguration
Section titled “Configuration”The FeatureFlagOptions class accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
BaseUrl | string | "https://eval.featureflip.io" | Base URL of the Evaluation API |
ConnectTimeout | TimeSpan | 5s | Timeout for establishing connections |
ReadTimeout | TimeSpan | 10s | Timeout for reading responses |
Streaming | bool | true | Enable SSE for real-time flag updates |
PollInterval | TimeSpan | 30s | Polling interval (used when streaming is disabled) |
FlushInterval | TimeSpan | 30s | Interval between event flush batches |
FlushBatchSize | int | 100 | Maximum number of events per flush batch |
InitTimeout | TimeSpan | 10s | Maximum time to wait for initial flag fetch |
WaitForInitialization | bool | false | Block the constructor until flags are loaded |
StartOffline | bool | false | Start the client even if initial flag loading fails |
Example configuration
Section titled “Example configuration”var options = new FeatureFlagOptions{ BaseUrl = "https://eval.featureflip.io", Streaming = true, PollInterval = TimeSpan.FromSeconds(60), FlushInterval = TimeSpan.FromSeconds(15), InitTimeout = TimeSpan.FromSeconds(30), WaitForInitialization = true,};Initialization
Section titled “Initialization”Create a client by providing an SDK key and optional options:
using var client = FeatureflipClient.Get( "sdk-dev-abc123", new FeatureFlagOptions { WaitForInitialization = true });SDK key resolution
Section titled “SDK key resolution”If sdkKey is null, the client falls back to the FEATUREFLIP_SDK_KEY environment variable. A FeatureFlagInitializationException is thrown if neither is available.
Initialization behavior
Section titled “Initialization behavior”The WaitForInitialization option controls whether the constructor blocks:
false(default): The constructor returns immediately and loads flags in the background. Evaluations return default values until initialization completes.true: The constructor blocks until flags are loaded orInitTimeoutexpires.
The StartOffline option allows the client to start even if initialization fails. The client will return default values and retry in the background.
IsInitialized
Section titled “IsInitialized”bool initialized = client.IsInitialized;Returns true once the client has successfully loaded flag data.
Evaluating Flags
Section titled “Evaluating Flags”All evaluation methods accept a flag key, an evaluation context, and a default value. If the flag is not found or an error occurs, the default value is returned.
Variation<T>()
Section titled “Variation<T>()”Generic evaluation method that works with any type:
var context = new EvaluationContext { UserId = "u-123" };bool enabled = client.Variation("dark-mode", context, false);Signature:
T Variation<T>(string key, EvaluationContext context, T defaultValue)BoolVariation()
Section titled “BoolVariation()”bool enabled = client.BoolVariation("dark-mode", context, false);StringVariation()
Section titled “StringVariation()”string plan = client.StringVariation("pricing-tier", context, "free");IntVariation()
Section titled “IntVariation()”int limit = client.IntVariation("rate-limit", context, 100);DoubleVariation()
Section titled “DoubleVariation()”double ratio = client.DoubleVariation("rollout-ratio", context, 0.5);JsonVariation<T>()
Section titled “JsonVariation<T>()”Deserializes a JSON flag value to the specified type:
var banner = client.JsonVariation<BannerConfig>( "banner-config", context, new BannerConfig { Text = "Welcome", Color = "#000" });Evaluation Details
Section titled “Evaluation Details”VariationDetail<T>()
Section titled “VariationDetail<T>()”Returns an EvaluationDetail<T> object with the evaluated value and metadata about the evaluation decision.
var detail = client.VariationDetail("dark-mode", context, false);
Console.WriteLine(detail.Value); // TrueConsole.WriteLine(detail.Reason); // RuleMatchConsole.WriteLine(detail.RuleId); // "rule-abc"Console.WriteLine(detail.ErrorMessage); // nullSignature:
EvaluationDetail<T> VariationDetail<T>(string key, EvaluationContext context, T defaultValue)EvaluationDetail<T>
Section titled “EvaluationDetail<T>”| Property | Type | Description |
|---|---|---|
Value | T | The evaluated flag value |
Reason | EvaluationReason | Why this value was returned |
RuleId | string? | The ID of the matched targeting rule |
ErrorMessage | string? | Error message if evaluation failed |
EvaluationReason
Section titled “EvaluationReason”| Value | Description |
|---|---|
RuleMatch | A targeting rule matched the context |
Fallthrough | No rules matched; the fallthrough variation was served |
FlagDisabled | The flag is disabled; the off variation was served |
FlagNotFound | The flag key does not exist |
Error | An error occurred during evaluation |
Evaluation Context
Section titled “Evaluation Context”The EvaluationContext class provides built-in properties and custom attributes for targeting rules and percentage rollouts:
var context = new EvaluationContext{ UserId = "u-123", Country = "US",};
// Add custom attributescontext.Set("plan", "pro");context.Set("team_size", 50);Built-in properties
Section titled “Built-in properties”| Property | Type | Description |
|---|---|---|
UserId | string? | Unique user identifier, used for percentage rollouts |
Email | string? | User email address |
Country | string? | User country code |
Custom attributes
Section titled “Custom attributes”Use Set() to add custom key-value attributes. Use GetAttribute() to retrieve any attribute (custom attributes take precedence over built-in properties):
context.Set("plan", "enterprise");object? plan = context.GetAttribute("plan"); // "enterprise"Cleanup
Section titled “Cleanup”The client implements IDisposable. Always dispose of the client when it is no longer needed to flush events and release resources.
Dispose()
Section titled “Dispose()”// Option 1: using declaration (recommended)using var client = FeatureflipClient.Get("sdk-key");
// Option 2: using blockusing (var client = FeatureflipClient.Get("sdk-key")){ // ...}
// Option 3: explicit disposalclient.Dispose();Flush()
Section titled “Flush()”Forces pending events to be sent to the server synchronously. Blocks for up to 5 seconds.
client.Flush();FlushAsync()
Section titled “FlushAsync()”Asynchronous version of Flush():
await client.FlushAsync(cancellationToken);Signature:
Task FlushAsync(CancellationToken cancellationToken = default)Dependency Injection
Section titled “Dependency Injection”The Featureflip.Client.Extensions.DependencyInjection package provides extension methods for registering the client with the ASP.NET Core DI container.
Register with SDK key
Section titled “Register with SDK key”builder.Services.AddFeatureflip("sdk-dev-abc123", options =>{ options.BaseUrl = "https://eval.featureflip.io"; options.Streaming = true; options.WaitForInitialization = true;});Register from configuration
Section titled “Register from configuration”builder.Services.AddFeatureflip( builder.Configuration.GetSection("Featureflip"));With appsettings.json:
{ "Featureflip": { "SdkKey": "sdk-dev-abc123", "BaseUrl": "https://eval.featureflip.io", "Streaming": true }}Register with options action
Section titled “Register with options action”builder.Services.AddFeatureflip(options =>{ options.SdkKey = "sdk-dev-abc123"; options.BaseUrl = "https://eval.featureflip.io"; options.WaitForInitialization = true;});Inject into services
Section titled “Inject into services”The client is registered as IFeatureflipClient (singleton):
public class MyService{ private readonly IFeatureflipClient _flags;
public MyService(IFeatureflipClient flags) { _flags = flags; }
public void DoWork() { var context = new EvaluationContext { UserId = "u-123" }; if (_flags.BoolVariation("new-feature", context, false)) { // feature is enabled } }}IFeatureflipClient
Section titled “IFeatureflipClient”The main interface for flag evaluation. Extends IDisposable.
public interface IFeatureflipClient : IDisposable{ bool IsInitialized { get; } T Variation<T>(string key, EvaluationContext context, T defaultValue); EvaluationDetail<T> VariationDetail<T>(string key, EvaluationContext context, T defaultValue); bool BoolVariation(string key, EvaluationContext context, bool defaultValue); string StringVariation(string key, EvaluationContext context, string defaultValue); int IntVariation(string key, EvaluationContext context, int defaultValue); double DoubleVariation(string key, EvaluationContext context, double defaultValue); T JsonVariation<T>(string key, EvaluationContext context, T defaultValue); void Flush(); Task FlushAsync(CancellationToken cancellationToken = default);}FeatureFlagInitializationException
Section titled “FeatureFlagInitializationException”Thrown when the client fails to initialize. This can occur when:
- The SDK key is missing or invalid
- The initial flag fetch times out (and
StartOfflineisfalse) - A network error prevents flag loading (and
StartOfflineisfalse)
See also
Section titled “See also”- C# Quickstart — get started in under 5 minutes
- Targeting & Segments — control which users see which variation
- Rollout Strategies — gradual percentage-based rollouts
- Evaluation API — REST API reference
- SDK Overview — compare server-side and client-side SDKs