Skip to main content

Installation

dotnet add package OmniFlags.Sdk
Requirements: .NET 8+, ASP.NET Core (for the hosted service integration)

Setup

Register OmniFlags in Program.cs. The SDK key is the only required configuration — polling interval, CDN endpoints, and all other operational settings are managed server-side and delivered with the snapshot.
// Program.cs
builder.Services.AddOmniFlags(options =>
{
    options.SdkKey = builder.Configuration["OmniFlags:SdkKey"]
        ?? throw new InvalidOperationException("OmniFlags:SdkKey is required.");
});
Store the SDK key in appsettings.json. Use environment-specific overrides (appsettings.Production.json) or secret management to keep production keys out of source control.
{
  "OmniFlags": {
    "SdkKey": "sk_live_..."
  }
}
AddOmniFlags registers:
  • OmniFlagsClient as a singleton in the DI container
  • A hosted service that fetches the initial snapshot at startup, before the application begins accepting traffic
By the time the first request arrives, the flag snapshot is loaded and OmniFlagsClient is ready to evaluate.

Evaluating flags

Inject OmniFlagsClient into services, controllers, or minimal API endpoints. The client is thread-safe and designed to be shared as a singleton.

In a service class

public class CheckoutService
{
    private readonly OmniFlagsClient _flags;

    public CheckoutService(OmniFlagsClient flags)
    {
        _flags = flags;
    }

    public OrderResult PlaceOrder(CheckoutRequest req)
    {
        var ctx = new EvaluationContext { CustomerId = req.CustomerId };
        var chargeDeliveryFee = _flags.IsEnabled("checkout.charge-delivery-fee", ctx);

        return new OrderResult
        {
            DeliveryFee = chargeDeliveryFee ? CalculateFee(req) : 0m,
        };
    }
}

In a minimal API endpoint

app.MapGet("/checkout", (string customerId, OmniFlagsClient flags) =>
{
    var ctx = new EvaluationContext
    {
        CustomerId = long.TryParse(customerId, out var id) ? id : null,
    };

    var chargeDeliveryFee = flags.IsEnabled("checkout.charge-delivery-fee", ctx);

    return Results.Ok(new { chargeDeliveryFee });
});

Client API

IsEnabled — boolean flags

bool IsEnabled(string flagKey, EvaluationContext? context = null, bool defaultValue = false)
Returns the flag value as a bool. The defaultValue is returned if the flag is not found, is disabled, or the client is not yet ready.
var chargeDeliveryFee = client.IsEnabled("checkout.charge-delivery-fee", ctx);

GetString — string flags

string GetString(string flagKey, string defaultValue, EvaluationContext? context = null)
var bannerColour = client.GetString("product-listing.promo-banner-colour", "blue", ctx);

GetNumber — number flags

double GetNumber(string flagKey, double defaultValue, EvaluationContext? context = null)
var maxCartItems = client.GetNumber("cart.max-items", 20, ctx);

Detail variants

Every typed method has a *Detail variant that returns the full EvaluationResult<T>. Use this when you need to inspect why a value was returned — useful for logging, analytics, or debugging evaluation logic.
bool IsEnabledDetail(string flagKey, bool defaultValue, EvaluationContext? context = null)
    → EvaluationResult<bool>

string GetStringDetail(string flagKey, string defaultValue, EvaluationContext? context = null)
    → EvaluationResult<string>

double GetNumberDetail(string flagKey, double defaultValue, EvaluationContext? context = null)
    → EvaluationResult<double>
var result = client.IsEnabledDetail("checkout.charge-delivery-fee", false, ctx);

logger.LogInformation(
    "Flag {Key} → {Value} (reason: {Reason}, rule: {RuleId})",
    "checkout.charge-delivery-fee",
    result.Value,
    result.Reason,
    result.RuleId
);
FieldTypeDescription
ValueTThe resolved flag value
Variantstring?The matched variant key, or null for boolean rollouts
ReasonEvaluationReasonWhy this value was returned — see reason codes
RuleIdstring?The ID of the targeting rule that matched, if any
ErrorCodeErrorCode?Set when Reason is Error

Evaluation context

Construct an EvaluationContext with the user and session attributes available at the call site. All properties are optional — include only what is meaningful for your targeting rules.
var ctx = new EvaluationContext
{
    CustomerId = 12345,
    Country    = "Nigeria",
    City       = "Lagos",
    Platform   = "api",
    AppVersion = "3.1.0",
};

// Pass custom attributes for rule matching
ctx["plan"] = "enterprise";
ctx["tier"] = "gold";
PropertyTypeNotes
CustomerIdlong?Primary bucketing key for rollout and traffic splits
AgentIdlong?Secondary bucketing key; used when CustomerId is absent
BusinessIdlong?Tertiary bucketing key
BusinessBranchIdlong?
Countrystring?Full country name (e.g. "Nigeria", "Ghana")
Citystring?
Platformstring?web, ios, android, api, etc.
AppVersionstring?Semver string
Additional key-value pairs set on the context dictionary are available for custom attribute matching in targeting rules, including dot-path traversal (e.g., ctx["user.plan"] = "enterprise").
Rollout and traffic splits require a bucketing key (CustomerId, AgentId, etc.) to produce a deterministic result. If no bucketing key is present, the SDK returns the flag’s default value with ErrorCode.MissingTargetingKey.

Startup behaviour

The hosted service registered by AddOmniFlags runs InitializeAsync during IHostedService.StartAsync, which is called by the ASP.NET Core runtime as part of the startup pipeline — before the application begins serving traffic. This means:
  • Flags are always ready on the first request. No warm-up period, no stale defaults on startup.
  • Startup failure does not block the application. If the CDN is unreachable at startup, the hosted service logs the error and the client falls back to an empty snapshot. The application starts, and evaluation returns caller-supplied defaults until the next successful poll.
Background polling continues throughout the application’s lifetime. The polling interval is set server-side and delivered in the snapshot.

Shutdown

OmniFlagsClient implements IAsyncDisposable. The DI container disposes the singleton as part of the host shutdown sequence. No manual teardown is required.