.NET SDK
Server-side feature flags for .NET 8+ with built-in circuit breaker, caching, SSE streaming, and zero external dependencies.
Circuit Breaker
Auto-recovery
Zero Dependencies
Only BCL types
Caching
Persistent cache
SSE Support
Real-time updates
Installation
dotnet add package Rollgate.SDK<PackageReference Include="Rollgate.SDK" Version="1.2.1" />Quick Start
using Rollgate.SDK;
var client = new RollgateClient(new RollgateConfig
{
ApiKey = Environment.GetEnvironmentVariable("ROLLGATE_API_KEY"),
BaseUrl = "https://api.rollgate.io",
RefreshInterval = TimeSpan.FromSeconds(30),
});
await client.InitAsync();
// Check flags
bool enabled = client.IsEnabled("my-feature", false);
// Identify user
await client.IdentifyAsync(new UserContext
{
Id = "user-123",
Email = "[email protected]",
Attributes = new Dictionary<string, object>
{
["plan"] = "pro",
},
});
// Cleanup
await client.CloseAsync();Configuration
| Option | Type | Default | Description |
|---|---|---|---|
| ApiKey | string | required | Server API key (rg_server_...) |
| BaseUrl | string | "https://api.rollgate.io" | API base URL |
| RefreshInterval | TimeSpan | 30s | Polling interval, TimeSpan.Zero to disable |
| EnableStreaming | bool | false | Use SSE for real-time updates (opt-in) |
| Timeout | TimeSpan | 5s | HTTP request timeout |
Methods
InitAsync()
Initialize the client and fetch flags from the server. Must be called before evaluating any flags.
await client.InitAsync();CloseAsync()
Gracefully shut down the client, stopping polling and SSE connections.
await client.CloseAsync();IsEnabled(key, defaultValue)
Check if a boolean flag is enabled. Returns the flag value or the default.
bool showFeature = client.IsEnabled("new-feature", false);
if (showFeature)
{
// New feature code
}GetValue<T>(key, defaultValue)
Get a flag value of any type. Use for string, number, or JSON flags.
// String flag
string theme = client.GetValue<string>("theme", "light");
// Number flag
int maxRetries = client.GetValue<int>("max-retries", 3);
// JSON flag
var config = client.GetValue<RateLimitConfig>("rate-limit-config", new RateLimitConfig
{
Enabled = false,
Limit = 100,
});GetString(key, defaultValue)
Type-safe helper for string flags.
string buttonColor = client.GetString("cta-color", "blue");
string variant = client.GetString("checkout-variant", "control");GetNumber(key, defaultValue)
Type-safe helper for number flags.
double pageSize = client.GetNumber("results-per-page", 20);
double timeout = client.GetNumber("api-timeout-ms", 5000);GetJson<T>(key, defaultValue)
Type-safe helper for JSON flags with generic type support. Uses System.Text.Json for deserialization.
public class FeatureConfig
{
public bool Enabled { get; set; }
public int MaxItems { get; set; }
public List<string> AllowedRoles { get; set; } = new();
}
var config = client.GetJson<FeatureConfig>("feature-config", new FeatureConfig
{
Enabled = false,
MaxItems = 10,
AllowedRoles = new List<string> { "admin" },
});
// Access typed properties
if (config.Enabled && config.AllowedRoles.Contains(user.Role))
{
// Feature logic
}GetAllFlags()
Get all flags as a dictionary.
Dictionary<string, object> flags = client.GetAllFlags();
// { "feature-a": true, "feature-b": false }IdentifyAsync(user)
Set user context for targeting rules. Re-fetches flags with the new context.
await client.IdentifyAsync(new UserContext
{
Id = "user-123",
Email = "[email protected]",
Attributes = new Dictionary<string, object>
{
["plan"] = "enterprise", // For "plan in pro,enterprise" rules
["country"] = "US", // For "country equals US" rules
["company"] = "Acme Inc", // For "company contains Acme" rules
["app_version"] = "2.1.0", // For "app_version semver_gt 2.0.0" rules
},
});RefreshAsync()
Force refresh flags from the server.
await client.RefreshAsync();Circuit Breaker
The SDK includes a circuit breaker that protects your app when Rollgate is unavailable. When open, it uses cached flag values.
// Get current state
string state = client.GetCircuitState();
// "closed" | "open" | "half-open"
// Listen to events
client.OnCircuitOpen += () =>
{
Console.WriteLine("Circuit breaker opened - using cached flags");
};
client.OnCircuitClosed += () =>
{
Console.WriteLine("Circuit breaker closed - normal operation");
};
// Force reset (use with caution)
client.ResetCircuit();ASP.NET Core Integration
Register the Rollgate client as a singleton in the dependency injection container for use across your application.
using Rollgate.SDK;
var builder = WebApplication.CreateBuilder(args);
// Register RollgateClient as a singleton
builder.Services.AddSingleton(sp =>
{
var client = new RollgateClient(new RollgateConfig
{
ApiKey = builder.Configuration["Rollgate:ApiKey"]!,
});
client.InitAsync().GetAwaiter().GetResult();
return client;
});
var app = builder.Build();
app.MapControllers();
app.Run();using Microsoft.AspNetCore.Mvc;
using Rollgate.SDK;
[ApiController]
[Route("api/[controller]")]
public class FeatureController : ControllerBase
{
private readonly RollgateClient _rollgate;
public FeatureController(RollgateClient rollgate) => _rollgate = rollgate;
[HttpGet("feature")]
public IActionResult GetFeature()
{
var enabled = _rollgate.IsEnabled("new-feature", false);
return Ok(new { enabled });
}
[HttpGet("health")]
public IActionResult Health()
{
var circuit = _rollgate.GetCircuitState();
var isHealthy = circuit == "closed";
return isHealthy ? Ok(new { status = "healthy", circuit })
: StatusCode(503, new { status = "degraded", circuit });
}
}// Middleware to identify user on each request
app.Use(async (context, next) =>
{
var rollgate = context.RequestServices.GetRequiredService<RollgateClient>();
var user = context.User;
if (user.Identity?.IsAuthenticated == true)
{
await rollgate.IdentifyAsync(new UserContext
{
Id = user.FindFirst("sub")?.Value ?? "",
Email = user.FindFirst("email")?.Value ?? "",
Attributes = new Dictionary<string, object>
{
["plan"] = user.FindFirst("plan")?.Value ?? "free",
},
});
}
await next();
});Error Handling & Graceful Degradation
The SDK is designed to never throw exceptions during flag evaluation. If Rollgate is unavailable, flags return their default values. Use these patterns for production-grade reliability:
// Default values ensure your app works even without Rollgate
bool showNewFeature = client.IsEnabled("new-feature", false);
// Returns false if: flag doesn't exist, API down, circuit open
// Monitor degraded state in health checks
app.MapGet("/health", (RollgateClient rollgate) =>
{
var circuit = rollgate.GetCircuitState();
var isHealthy = circuit == "closed";
return Results.Json(new
{
status = isHealthy ? "healthy" : "degraded",
circuit,
flagsSource = circuit == "open" ? "cache" : "live",
}, statusCode: isHealthy ? 200 : 503);
});Initialization Error Handling
try
{
await client.InitAsync();
Console.WriteLine($"Rollgate initialized with {client.GetAllFlags().Count} flags");
}
catch (Exception ex)
{
// Log but don't crash - SDK will use defaults
Console.Error.WriteLine($"Rollgate init failed: {ex.Message}");
Console.WriteLine("Continuing with default flag values");
}
// App starts regardless of Rollgate status
app.Run();