Skip to content

C# SDK Reference

TL;DR: Install Featureflip.Client, call client.BoolVariation("flag-key", context, false), and you’re evaluating flags — synchronous, no await in 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.

View source on GitHub

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.

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.

The SDK multi-targets the following frameworks:

TargetMinimum Version
.NET8.0
.NET10.0
.NET Standard2.0 (.NET Framework 4.6.1+, .NET Core 2.0+)
Terminal window
dotnet add package Featureflip.Client

For ASP.NET Core dependency injection support, also install the extensions package:

Terminal window
dotnet add package Featureflip.Client.Extensions.DependencyInjection

The FeatureFlagOptions class accepts the following options:

OptionTypeDefaultDescription
BaseUrlstring"https://eval.featureflip.io"Base URL of the Evaluation API
ConnectTimeoutTimeSpan5sTimeout for establishing connections
ReadTimeoutTimeSpan10sTimeout for reading responses
StreamingbooltrueEnable SSE for real-time flag updates
PollIntervalTimeSpan30sPolling interval (used when streaming is disabled)
FlushIntervalTimeSpan30sInterval between event flush batches
FlushBatchSizeint100Maximum number of events per flush batch
InitTimeoutTimeSpan10sMaximum time to wait for initial flag fetch
WaitForInitializationboolfalseBlock the constructor until flags are loaded
StartOfflineboolfalseStart the client even if initial flag loading fails
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,
};

Create a client by providing an SDK key and optional options:

using var client = FeatureflipClient.Get(
"sdk-dev-abc123",
new FeatureFlagOptions { WaitForInitialization = true }
);

If sdkKey is null, the client falls back to the FEATUREFLIP_SDK_KEY environment variable. A FeatureFlagInitializationException is thrown if neither is available.

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 or InitTimeout expires.

The StartOffline option allows the client to start even if initialization fails. The client will return default values and retry in the background.

bool initialized = client.IsInitialized;

Returns true once the client has successfully loaded flag data.

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.

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)
bool enabled = client.BoolVariation("dark-mode", context, false);
string plan = client.StringVariation("pricing-tier", context, "free");
int limit = client.IntVariation("rate-limit", context, 100);
double ratio = client.DoubleVariation("rollout-ratio", context, 0.5);

Deserializes a JSON flag value to the specified type:

var banner = client.JsonVariation<BannerConfig>(
"banner-config",
context,
new BannerConfig { Text = "Welcome", Color = "#000" }
);

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); // True
Console.WriteLine(detail.Reason); // RuleMatch
Console.WriteLine(detail.RuleId); // "rule-abc"
Console.WriteLine(detail.ErrorMessage); // null

Signature:

EvaluationDetail<T> VariationDetail<T>(string key, EvaluationContext context, T defaultValue)
PropertyTypeDescription
ValueTThe evaluated flag value
ReasonEvaluationReasonWhy this value was returned
RuleIdstring?The ID of the matched targeting rule
ErrorMessagestring?Error message if evaluation failed
ValueDescription
RuleMatchA targeting rule matched the context
FallthroughNo rules matched; the fallthrough variation was served
FlagDisabledThe flag is disabled; the off variation was served
FlagNotFoundThe flag key does not exist
ErrorAn error occurred during evaluation

The EvaluationContext class provides built-in properties and custom attributes for targeting rules and percentage rollouts:

var context = new EvaluationContext
{
UserId = "u-123",
Email = "[email protected]",
Country = "US",
};
// Add custom attributes
context.Set("plan", "pro");
context.Set("team_size", 50);
PropertyTypeDescription
UserIdstring?Unique user identifier, used for percentage rollouts
Emailstring?User email address
Countrystring?User country code

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"

The client implements IDisposable. Always dispose of the client when it is no longer needed to flush events and release resources.

// Option 1: using declaration (recommended)
using var client = FeatureflipClient.Get("sdk-key");
// Option 2: using block
using (var client = FeatureflipClient.Get("sdk-key"))
{
// ...
}
// Option 3: explicit disposal
client.Dispose();

Forces pending events to be sent to the server synchronously. Blocks for up to 5 seconds.

client.Flush();

Asynchronous version of Flush():

await client.FlushAsync(cancellationToken);

Signature:

Task FlushAsync(CancellationToken cancellationToken = default)

The Featureflip.Client.Extensions.DependencyInjection package provides extension methods for registering the client with the ASP.NET Core DI container.

builder.Services.AddFeatureflip("sdk-dev-abc123", options =>
{
options.BaseUrl = "https://eval.featureflip.io";
options.Streaming = true;
options.WaitForInitialization = true;
});
builder.Services.AddFeatureflip(
builder.Configuration.GetSection("Featureflip")
);

With appsettings.json:

{
"Featureflip": {
"SdkKey": "sdk-dev-abc123",
"BaseUrl": "https://eval.featureflip.io",
"Streaming": true
}
}
builder.Services.AddFeatureflip(options =>
{
options.SdkKey = "sdk-dev-abc123";
options.BaseUrl = "https://eval.featureflip.io";
options.WaitForInitialization = true;
});

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
}
}
}

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);
}

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 StartOffline is false)
  • A network error prevents flag loading (and StartOffline is false)