Skip to main content

.NET SDK Architecture

This document provides a comprehensive overview of the Toggly.FeatureManagement .NET libraries architecture, their components, and how they work together to provide feature flag management capabilities.

Overview​

The Toggly.FeatureManagement .NET libraries extend Microsoft's Microsoft.FeatureManagement library with additional capabilities including:

  • Real-time feature updates via WebSocket connections
  • Signed feature definitions for enhanced security
  • Feature state change notifications with callback support
  • Usage statistics tracking and metrics collection
  • Snapshot providers for offline/cached feature definitions
  • Secure feature authorization for additional security layers
  • Integration with Hangfire for feature-gated recurring jobs
  • NSwag integration for feature-aware API documentation

Core Library Structure​

The Toggly.FeatureManagement ecosystem consists of several NuGet packages:

Toggly.FeatureManagement (Core Library)​

The main library that provides the foundation for all Toggly feature management functionality.

Key Components:

TogglyFeatureProvider​

The central component that fetches and manages feature flag definitions from the Toggly service.

Responsibilities:

  • Fetches feature definitions from https://definitions.toggly.io/definitions/{appKey}/{environment} (or definitions-signed when signed definitions are enabled)
  • Supports signed definitions with ECDSA signature verification (ES256)
  • Maintains in-memory cache of feature definitions
  • Implements polling (every 5 minutes) and WebSocket-based real-time updates
  • Loads snapshots from IFeatureSnapshotProvider on startup
  • Manages JWK (JSON Web Key) sets for signature verification
  • Tracks secure features that require additional authorization
  • Maps metrics to features for experiment tracking

Key Features:

  • ETag Support: Uses HTTP ETags to minimize unnecessary data transfer
  • Signature Verification: Validates signed definitions using ECDSA (ES256) with JWK sets
  • WebSocket Live Updates: Establishes WebSocket connection for instant feature updates
  • Snapshot Loading: Loads cached definitions on startup for faster initialization
  • Thread-Safe Operations: Uses semaphores and concurrent collections for thread safety
  • Key ID Whitelisting: Optional whitelist of allowed key IDs for enhanced security

Configuration:

services.AddToggly(options => {
options.AppKey = "your-app-key";
options.Environment = "Production";
options.UseSignedDefinitions = true; // Enable signature verification
options.AllowedKeyIds = new HashSet<string> { "key-id-1", "key-id-2" }; // Optional whitelist
});

TogglyFeatureManager​

A decorator around IFeatureManager that adds:

  • Automatic usage statistics recording
  • Secure feature authorization checks
  • Feature check tracking

Flow:

  1. Checks feature state via underlying IFeatureManager
  2. If enabled and feature is secured, performs additional authorization check
  3. Records usage statistics via IFeatureUsageStatsProvider
  4. Returns final decision

TogglyFeatureStateService​

Manages feature state change notifications and callbacks.

Capabilities:

  • Register callbacks for when features turn on/off
  • Track current state of features
  • Notify subscribers when definitions change
  • Support for both string and enum-based feature keys

Usage Example:

var stateService = serviceProvider.GetRequiredService<IFeatureStateService>();

// Register callback when feature turns on
var callbackId = stateService.WhenFeatureTurnsOn("MyFeature", () => {
// Feature just turned on - perform initialization
InitializeNewFeature();
});

// Unregister when done
stateService.UnregisterFeatureStateChange("MyFeature", callbackId);

TogglyUsageStatsProvider​

Collects and sends feature usage statistics to Toggly.

Statistics Collected:

  • Enabled/Disabled Counts: Number of times feature was checked and result
  • Unique Users: Track unique users who saw enabled/disabled features
  • Usage Counts: Track when features are actually used (not just checked)
  • Unique Request Counts: Track unique requests where feature was checked
  • Monthly Unique Users: Track unique user hashes for monthly analytics

Data Flow:

  1. Statistics collected in-memory using concurrent dictionaries
  2. Batched and sent every minute via gRPC
  3. Uses user context from IFeatureContextProvider for unique user tracking
  4. Sends incremental unique user hashes for server-side deduplication

Memory Management:

  • Limits unique user hash tracking to prevent unbounded growth (10,000 per feature)
  • Clears tracking data after successful send
  • Restores data on error for retry

TogglyMetricsService​

Collects and sends custom metrics related to feature experiments.

Metric Types:

  • Measurements: Aggregated values over time (like trip odometer)
  • Observations: Point-in-time values (like fuel gauge)
  • Counters: Incremental counters (like odometer)

Features:

  • Automatically tracks metrics for features associated with experiments
  • Supports context-aware metric collection
  • Integrates with IMetricsRegistryService for custom metric sources
  • Sends metrics via gRPC every minute

Usage Example:

var metricsService = serviceProvider.GetRequiredService<IMetricsService>();

// Record a measurement
await metricsService.MeasureAsync("checkout-completed", 125.50);

// Record an observation
await metricsService.ObserveAsync("active-users", 1500);

// Increment a counter
await metricsService.IncrementCounterAsync("api-calls", 1);

Toggly.FeatureManagement.Web​

Provides ASP.NET Core-specific integrations and HTTP context support.

Key Components:

HttpFeatureContextProvider​

Implements IFeatureContextProvider using HTTP context information.

Context Information:

  • Unique Identifier: Uses authenticated user name or IP address
  • Request Tracking: Tracks which features were accessed in current request
  • Session Support: Can use session ID if available

HttpContextTargetingContextAccessor​

Provides targeting context from HTTP request for user-based targeting.

Extracts:

  • User ID from HttpContext.User.Identity.Name
  • Groups from claims (claim type "group")
  • Caches targeting context in HttpContext.Items for performance

Feature Filters​

Web-specific feature filters for HTTP context (see also the Feature filters reference and SDK × filter matrix):

  • BrowserFamilyFilter: Target by browser family (Chrome, Firefox, etc.)
  • BrowserLanguageFilter: Target by browser language
  • CountryFilter: Target by country (from IP geolocation)
  • DeviceTypeFilter: Target by the UAParser device family derived from the User-Agent, not a generic Mobile/Desktop/Tablet classification. The family depends on the parser and user agent; inspect the actual value when configuring a filter. It is distinct from the OS family.
  • OSFilter: Target by operating system
  • UserClaimsFilter: Target by user claims/roles

These filters register with AddTogglyWeb and read ambient HttpContext. Console / worker hosts without the Web package do not get them — use IFeatureManager.IsEnabledAsync(feature, context) with an app context, or keep segment evaluation on the Definitions worker for clients.

Registration:

services.AddTogglyWeb(options => {
options.AppKey = "your-app-key";
options.Environment = "Production";
});

Toggly.FeatureManagement.Storage.DistributedCache​

Provides snapshot storage using ASP.NET Core's distributed cache (Redis, SQL Server, etc.).

Features:

  • Stores feature definitions in distributed cache
  • Stores JWK sets for signature verification
  • Supports cache expiration and refresh
  • Thread-safe snapshot operations

Usage:

services.AddDistributedMemoryCache(); // or Redis, SQL Server, etc.
services.AddTogglyDistributedCacheSnapshotProvider(options => {
options.DocumentName = "TogglyFeatureSnapshots";
});

Toggly.FeatureManagement.Storage.RavenDB​

Provides snapshot storage using RavenDB document database.

Features:

  • Stores feature definitions as RavenDB documents
  • Stores JWK sets separately
  • Supports document versioning
  • Optimized for RavenDB's document model

Usage:

services.AddSingleton<IDocumentStore>(store);
services.AddTogglyRavenDBSnapshotProvider(options => {
options.DocumentName = "FeatureSnapshots/Toggly";
options.JwkDocumentName = "JwkSnapshots/Toggly";
});

Toggly.Storage.EntityFramework​

Provides snapshot storage using Entity Framework Core.

Features:

  • Stores feature definitions in SQL database
  • Supports any EF Core-compatible database
  • Standard relational database storage

Toggly.FeatureManagement.NSwag​

Integrates with NSwag to provide feature-aware OpenAPI/Swagger documentation.

Features:

  • Automatically filters out API endpoints based on feature flags
  • Uses [FeatureGate] attributes to determine visibility
  • Updates Swagger documentation in real-time based on feature states

Usage:

services.AddOpenApiDocument(document => {
document.AddFeatureGateFiltering(serviceProvider);
});

app.UseFeatureAwareOpenApi();

Toggly.FeatureManagement.Hangfire​

Integrates with Hangfire to enable feature-gated recurring jobs.

Features:

  • Automatically register recurring jobs when features turn on
  • Remove recurring jobs when features turn off
  • Support for all Hangfire job types (sync, async, generic)

Usage:

var stateService = serviceProvider.GetRequiredService<IFeatureStateService>();

stateService.AddOrUpdateJob(
featureKey: "ScheduledReportFeature",
methodCall: () => ReportService.GenerateReport(),
cronExpression: "0 0 * * *" // Daily at midnight
);

Toggly.Metrics.SystemMetrics​

Extends metrics collection with system performance counters.

Features:

  • Captures Windows Performance Counters
  • Integrates with IMetricsRegistryService
  • Provides system-level metrics for feature experiments

Data Models​

FeatureDefinitionModel​

Represents a feature flag definition from Toggly.

public class FeatureDefinitionModel
{
public string FeatureKey { get; set; }
public List<FeatureFilter> Filters { get; set; }
public List<string>? Metrics { get; set; }
public bool SecuredFeature { get; set; }
public RequirementType RequirementType { get; set; }
}

Properties:

  • FeatureKey: Unique identifier for the feature
  • Filters: List of feature filters (AlwaysOn, Percentage, TimeWindow, Targeting, etc.)
  • Metrics: Associated metrics for experiment tracking
  • SecuredFeature: Whether feature requires additional authorization
  • RequirementType: Whether all filters must pass (All) or any filter (Any)

SignedDefinitionsResponse​

Response model for signed feature definitions.

public class SignedDefinitionsResponse
{
public List<FeatureDefinitionModel> Defs { get; set; }
public string Signature { get; set; }
public string Kid { get; set; } // Key ID
public long Timestamp { get; set; }
}

Interfaces​

IFeatureStateService​

Manages feature state change notifications.

Methods:

  • WhenFeatureTurnsOn(featureKey, action): Register callback for feature turning on
  • WhenFeatureTurnsOff(featureKey, action): Register callback for feature turning off
  • UnregisterFeatureStateChange(featureKey, id): Remove callback
  • WhenDefinitionsChange(action): Register callback for definition changes

IFeatureSnapshotProvider​

Provides snapshot storage for feature definitions.

Methods:

  • SaveSnapshotAsync(features, signature, keyId, timestamp): Save feature snapshot
  • GetFeaturesSnapshotAsync(): Retrieve feature snapshot
  • SaveJwkSnapshot(jwks, timestamp): Save JWK snapshot
  • GetJwkSnapshotAsync(): Retrieve JWK snapshot

IFeatureContextProvider​

Provides context information for feature evaluation and statistics.

Methods:

  • AccessedInRequestAsync(featureName): Check if feature was accessed in current request
  • GetContextIdentifierAsync(): Get unique identifier (user ID, IP, etc.)

ISecureFeatureProvider​

Identifies features that require additional security checks.

Methods:

  • IsFeatureSecured(featureKey): Check if feature requires security authorization

IFeatureAuthorizationService​

Provides additional authorization for secured features.

Methods:

  • IsAllowedAsync(featureKey): Check if current context is authorized for feature

IMetricsService​

Collects and sends custom metrics.

Methods:

  • MeasureAsync(metricKey, value): Record aggregated measurement
  • ObserveAsync(metricKey, value): Record point-in-time observation
  • IncrementCounterAsync(metricKey, value): Increment counter

IMetricsRegistryService​

Registers custom metric sources.

Methods:

  • RegisterMeasurements(action): Register callback for measurements
  • RegisterObservations(action): Register callback for observations
  • RegisterCounters(action): Register callback for counters

Communication Protocols​

HTTP REST API​

Primary communication method for fetching feature definitions.

Endpoints (https://definitions.toggly.io):

  • GET /definitions/{appKey}/{environment}: Get feature definitions (unsigned)
  • GET /definitions-signed/{appKey}/{environment}: Get signed feature definitions
  • GET /.well-known/jwks: Get JSON Web Key Set for signature verification

Features:

  • ETag support for conditional requests
  • Exponential backoff retry policy (8 retries, 2^attempt seconds)
  • HTTP/2 support
  • Automatic decompression (GZip, Deflate)

WebSocket​

Real-time sync signals for feature definition changes on the definitions worker.

Client-side flow:

  1. Connect to wss://definitions.toggly.io/{appKey}/ws?rev={cachedRevision}
  2. Receive sync on connect; skip HTTP when unchanged: true
  3. On flags-updated, refresh only when revision differs
  4. On signing-key-updated, refetch JWKS and definitions

Server-side (.NET) flow:

  1. Connect to wss://definitions.toggly.io/{appKey}/ws?rev={cachedRevision}
  2. Receive sync on connect; skip HTTP when unchanged: true
  3. On flags-updated, refresh only when revision differs
  4. On signing-key-updated, refetch JWKS and definitions

See WebSocket sync (client-side) for the shared protocol.

Benefits:

  • Instant feature updates without unnecessary HTTP on reconnect
  • Conditional HTTP (If-None-Match) reduces payload size
  • Lower latency for feature changes

gRPC​

High-performance protocol for metrics and usage statistics.

Services:

  • Metrics.MetricsClient: Sends experiment metrics
  • Usage.UsageClient: Sends feature usage statistics

Features:

  • Automatic retry with exponential backoff
  • gRPC-Web support for browser compatibility
  • Metadata support (User-Agent, etc.)
  • Streaming support for high-throughput scenarios

Security Features​

Signed Definitions​

When UseSignedDefinitions is enabled:

  1. Feature definitions are signed using ECDSA (ES256)
  2. Signature includes timestamp to prevent replay attacks
  3. JWK sets fetched from /.well-known/jwks endpoint
  4. Key IDs verified against whitelist (if configured)
  5. Signatures verified before accepting definitions

Security Benefits:

  • Prevents tampering with feature definitions
  • Ensures definitions come from authorized Toggly instance
  • Supports key rotation via JWK sets

Secure Features​

Features can be marked as "secured" requiring additional authorization:

  1. Feature definition includes SecuredFeature: true
  2. TogglyFeatureManager checks ISecureFeatureProvider
  3. If secured, calls IFeatureAuthorizationService.IsAllowedAsync()
  4. Only returns enabled if both feature flag and authorization pass

Use Cases:

  • Compliance requirements
  • Additional security layers
  • Role-based access control

Dependency Injection Patterns​

Service Decoration​

The library uses service decoration to extend IFeatureManager:

services.Decorate<IFeatureManager, TogglyFeatureManager>();

This pattern allows:

  • Extending existing services without modifying original implementation
  • Maintaining compatibility with Microsoft.FeatureManagement
  • Adding cross-cutting concerns (logging, metrics, etc.)

Feature-Based Service Registration​

Services can be registered conditionally based on feature flags:

// Register service only when feature is enabled
services.AddTransientForFeature<IPaymentService, NewPaymentService>("NewPaymentFeature");

// Decorate service when feature is enabled
services.DecorateForFeature<IPaymentService, PaymentServiceDecorator>("NewPaymentFeature");

Benefits:

  • Clean separation of feature implementations
  • Automatic service switching based on feature state
  • No feature checks needed in consuming code

Error Handling & Resilience​

Retry Policies​

HTTP Client:

  • Exponential backoff: 2^attempt seconds (max 8 retries)
  • Handles transient errors and 404 responses
  • 60-minute handler lifetime

gRPC Client:

  • Max 10 attempts
  • Initial backoff: 1 second
  • Max backoff: 10 seconds
  • Backoff multiplier: 1.5
  • Retries on: Unavailable, DataLoss, Aborted, DeadlineExceeded, etc.

Snapshot Fallback​

If API is unavailable:

  1. Loads snapshot from IFeatureSnapshotProvider on startup
  2. Continues using cached definitions
  3. Attempts refresh in background
  4. Updates snapshot when API becomes available

Graceful Degradation​

  • Features default to disabled if undefined (configurable for development)
  • Statistics and metrics queued if API unavailable
  • WebSocket failures don't block feature evaluation
  • Errors logged but don't crash application

Performance Optimizations​

Caching​

  • In-Memory Cache: Feature definitions cached in ConcurrentDictionary
  • ETag Support: Minimizes data transfer with conditional requests
  • Snapshot Loading: Fast startup with cached definitions
  • JWK Caching: Public keys cached for 30 days

Batching​

  • Statistics Batching: Usage stats batched and sent every minute
  • Metrics Batching: Metrics batched and sent every minute
  • Unique User Tracking: Hashed user IDs reduce memory by ~80%

Thread Safety​

  • Concurrent Collections: All shared state uses thread-safe collections
  • Semaphores: Prevents concurrent refresh operations
  • Atomic Operations: Statistics use atomic increment operations

Configuration​

TogglySettings​

public class TogglySettings
{
public string AppKey { get; set; }
public string Environment { get; set; } = "Production";
public bool UseSignedDefinitions { get; set; }
public string? BaseUrl { get; set; }
public string? AppVersion { get; set; }
public string? InstanceName { get; set; }
public bool UndefinedEnabledOnDevelopment { get; set; }
public HashSet<string>? AllowedKeyIds { get; set; }
}

TogglySnapshotSettings​

public class TogglySnapshotSettings
{
public string? DocumentName { get; set; }
public string? JwkDocumentName { get; set; }
}

Integration Examples​

Basic Setup​

services.AddToggly(options => {
options.AppKey = Configuration["Toggly:AppKey"];
options.Environment = Configuration["Toggly:Environment"];
});

services.AddTogglyFeatureManagement();

With Web Support​

services.AddTogglyWeb(options => {
options.AppKey = Configuration["Toggly:AppKey"];
options.Environment = Configuration["Toggly:Environment"];
});

With Signed Definitions and Snapshot Provider​

services.AddToggly(options => {
options.AppKey = Configuration["Toggly:AppKey"];
options.Environment = Configuration["Toggly:Environment"];
options.UseSignedDefinitions = true;
options.AllowedKeyIds = new HashSet<string> { "key-id-1" };
});

services.AddDistributedMemoryCache();
services.AddTogglyDistributedCacheSnapshotProvider();

services.AddTogglyFeatureManagement();

With Feature State Handlers​

var stateService = serviceProvider.GetRequiredService<IFeatureStateService>();

stateService.WhenFeatureTurnsOn("NewFeature", () => {
// Initialize feature-specific services
InitializeNewFeature();
});

stateService.WhenFeatureTurnsOff("NewFeature", () => {
// Cleanup feature-specific resources
CleanupNewFeature();
});

Best Practices​

  1. Use Snapshot Providers: Always configure a snapshot provider for production to ensure fast startup and offline support

  2. Enable Signed Definitions: Use signed definitions in production for enhanced security

  3. Configure Key Whitelisting: When using signed definitions, configure AllowedKeyIds to restrict which keys are accepted

  4. Monitor Statistics: Use IFeatureUsageStatsProvider to track feature adoption and usage

  5. Use Feature State Handlers: Register handlers for feature state changes to perform initialization/cleanup

  6. Implement Custom Context Providers: Implement IFeatureContextProvider to provide user-specific context for targeting

  7. Use Metrics for Experiments: Associate metrics with features to track experiment results

  8. Handle Errors Gracefully: Ensure your application handles cases where Toggly API is unavailable

  9. Configure Appropriate Timeouts: Adjust HTTP and gRPC timeouts based on your network conditions

  10. Use Feature-Based Service Registration: Leverage AddTransientForFeature and DecorateForFeature for clean feature implementations

Troubleshooting​

Debug Information​

All major components provide debug information:

var provider = serviceProvider.GetRequiredService<IFeatureProviderDebug>();
var debugInfo = provider.GetDebugInfo();

var metrics = serviceProvider.GetRequiredService<IMetricsDebug>();
var metricsInfo = metrics.GetDebugInfo();

var usage = serviceProvider.GetRequiredService<IUsageStatsDebug>();
var usageInfo = usage.GetDebugInfo();

Common Issues​

  1. Features not updating: Check WebSocket connection status in debug info
  2. Signature verification failing: Verify JWK set is accessible and key ID matches
  3. Statistics not sending: Check gRPC connection and error logs
  4. High memory usage: Check unique user hash limits and send frequency

Next Steps​