.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}(ordefinitions-signedwhen 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
IFeatureSnapshotProvideron 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:
- Checks feature state via underlying
IFeatureManager - If enabled and feature is secured, performs additional authorization check
- Records usage statistics via
IFeatureUsageStatsProvider - 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:
- Statistics collected in-memory using concurrent dictionaries
- Batched and sent every minute via gRPC
- Uses user context from
IFeatureContextProviderfor unique user tracking - 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
IMetricsRegistryServicefor 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.Itemsfor 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 onWhenFeatureTurnsOff(featureKey, action): Register callback for feature turning offUnregisterFeatureStateChange(featureKey, id): Remove callbackWhenDefinitionsChange(action): Register callback for definition changes
IFeatureSnapshotProvider
Provides snapshot storage for feature definitions.
Methods:
SaveSnapshotAsync(features, signature, keyId, timestamp): Save feature snapshotGetFeaturesSnapshotAsync(): Retrieve feature snapshotSaveJwkSnapshot(jwks, timestamp): Save JWK snapshotGetJwkSnapshotAsync(): Retrieve JWK snapshot
IFeatureContextProvider
Provides context information for feature evaluation and statistics.
Methods:
AccessedInRequestAsync(featureName): Check if feature was accessed in current requestGetContextIdentifierAsync(): 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 measurementObserveAsync(metricKey, value): Record point-in-time observationIncrementCounterAsync(metricKey, value): Increment counter
IMetricsRegistryService
Registers custom metric sources.
Methods:
RegisterMeasurements(action): Register callback for measurementsRegisterObservations(action): Register callback for observationsRegisterCounters(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 definitionsGET /.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:
- Connect to
wss://definitions.toggly.io/{appKey}/ws?rev={cachedRevision} - Receive
syncon connect; skip HTTP whenunchanged: true - On
flags-updated, refresh only when revision differs - On
signing-key-updated, refetch JWKS and definitions
Server-side (.NET) flow:
- Connect to
wss://definitions.toggly.io/{appKey}/ws?rev={cachedRevision} - Receive
syncon connect; skip HTTP whenunchanged: true - On
flags-updated, refresh only when revision differs - 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:
- Feature definitions are signed using ECDSA (ES256)
- Signature includes timestamp to prevent replay attacks
- JWK sets fetched from
/.well-known/jwksendpoint - Key IDs verified against whitelist (if configured)
- 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:
- Feature definition includes
SecuredFeature: true TogglyFeatureManagerchecksISecureFeatureProvider- If secured, calls
IFeatureAuthorizationService.IsAllowedAsync() - 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:
- Loads snapshot from
IFeatureSnapshotProvideron startup - Continues using cached definitions
- Attempts refresh in background
- 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
-
Use Snapshot Providers: Always configure a snapshot provider for production to ensure fast startup and offline support
-
Enable Signed Definitions: Use signed definitions in production for enhanced security
-
Configure Key Whitelisting: When using signed definitions, configure
AllowedKeyIdsto restrict which keys are accepted -
Monitor Statistics: Use
IFeatureUsageStatsProviderto track feature adoption and usage -
Use Feature State Handlers: Register handlers for feature state changes to perform initialization/cleanup
-
Implement Custom Context Providers: Implement
IFeatureContextProviderto provide user-specific context for targeting -
Use Metrics for Experiments: Associate metrics with features to track experiment results
-
Handle Errors Gracefully: Ensure your application handles cases where Toggly API is unavailable
-
Configure Appropriate Timeouts: Adjust HTTP and gRPC timeouts based on your network conditions
-
Use Feature-Based Service Registration: Leverage
AddTransientForFeatureandDecorateForFeaturefor 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
- Features not updating: Check WebSocket connection status in debug info
- Signature verification failing: Verify JWK set is accessible and key ID matches
- Statistics not sending: Check gRPC connection and error logs
- High memory usage: Check unique user hash limits and send frequency
Next Steps
- Learn about .NET SDK Configuration
- Explore Advanced Usage Patterns
- Understand Snapshot Providers
- Review State Change Handlers