toggly
  • What is toggly?
  • 🚀Getting started
    • Creating your first project
    • Using our Feature Flag Demo
    • Inviting Your Team
    • API Documentation
  • 🔘Feature Flags
    • Feature Flags in C# / .NET
      • Views
      • Controllers & Actions
      • Dependency Injection
      • Routing
      • IFeatureManagerSnapshot
      • Persistent Flags Across Requests
      • Disabled Action Handling
      • State Change Handlers
      • Custom Context
      • Snapshot Providers
        • RavenDB
        • Entity Framework
      • Debugging Endpoint
      • Serving Front-end Flags
      • Undefined Features In Development
      • Deployments and Version
    • Feature Flags in Vue.js
      • Feature Component
      • Directly Checking a Flag
      • Users and Rollouts
      • Flag Defaults
    • Feature Flags in JavaScript
      • Directly Checking a Flag
      • Definition Refresh
      • Users and Rollouts
      • Flag Defaults
    • Feature Flags in Flutter
      • Feature Widget
      • Directly Checking a Flag
      • Users and Rollouts
      • Flag Defaults
    • Feature Flags in HTML/CSS
  • 📈Metrics
    • Metrics in C# / .NET
      • Feature Usage
      • Business Metrics
      • Performance Metrics
  • 👩‍💻Use Cases
    • For Engineers
      • Develop continuously, turn on when ready
    • For Product Managers
      • Measure Feature Impact
    • For Agile Teams
      • Faster QA Cycles
      • Streamline Your Releases
Powered by GitBook
On this page
  • Persisting flags in the user's Session
  • Persisting flags in Redis

Was this helpful?

  1. Feature Flags
  2. Feature Flags in C# / .NET

Persistent Flags Across Requests

Most feature flag definitions result into the flag being either on or off based on an outside condition such as AlwaysOn/AlwaysOff, TimeWindow and Targetting when at 100%.

Enabling the flag for only a percentage of requests can result in the user having different values between requests, so the state needs to be stored. This is done by implementing the ISessionManager interface.

Persisting flags in the user's Session

public class HttpContextFeatureSessionManager : ISessionManager
{
    private readonly IHttpContextAccessor _contextAccessor;
    private const string SessionKeyPrefix = "feature_flag_";

    public HttpContextFeatureSessionManager(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public Task<bool?> GetAsync(string featureName)
    {
        bool keyExistsInHttpSession = _contextAccessor.HttpContext!
                      .Session
                      .TryGetValue(key: $"{SessionKeyPrefix}{featureName}",
                                   value: out byte[]? bytes);

        if (keyExistsInHttpSession)
        {
            return Task.FromResult((bool?)BitConverter.ToBoolean(bytes));
        }

        return Task.FromResult<bool?>(null);
    }

    public Task SetAsync(string featureName, bool enabled)
    {
        _contextAccessor.HttpContext!
                        .Session
                        .Set(key: $"{SessionKeyPrefix}{featureName}",
                             value: BitConverter.GetBytes(enabled));

        return Task.CompletedTask;
    }
}

Then it can be registered in Startup.cs

services.AddTransient<ISessionManager, HttpContextFeatureSessionManager>();
services.AddTogglyWeb(options =>
    {
        options.AppKey = Configuration["Toggly:AppKey"];
        options.Environment = Configuration["Toggly:Environment"];
    });

Persisting flags in Redis

The example below uses a query variable u as a user diferentiator, and caches the values for 30 minutes.

public class RedisSessionManager : ISessionManager
{
    private readonly IDatabase _cache;
    private readonly IHttpContextAccessor _contextAccessor;
    private readonly IMemoryCache _memoryCache;
    private readonly IFeatureDefinitionProvider _featureDefinitionProvider;

    public RedisSessionManager(IDatabase cache, IHttpContextAccessor contextAccessor, IMemoryCache memoryCache, IFeatureDefinitionProvider featureDefinitionProvider)
    {
        _cache = cache;
        _contextAccessor = contextAccessor;
        _memoryCache = memoryCache;
        _featureDefinitionProvider = featureDefinitionProvider;
    }

    public async Task<bool?> GetAsync(string featureName)
    {
        if (!_contextAccessor.HttpContext!.Request.Query.ContainsKey("u")) return null;

        var username = _contextAccessor.HttpContext!.Request.Query["u"];
        
        var val = await _cache.HashGetAsync($"tc:{username}", featureName);

        if (val.HasValue)
        {
            await _cache.KeyExpireAsync($"tc:{username}", new TimeSpan(0, 30, 0));
            return val.Equals(1);
        }

        return null;
    }

    public async Task SetAsync(string featureName, bool enabled)
    {
        if (!_contextAccessor.HttpContext!.Request.Query.ContainsKey("u")) return;

        var username = _contextAccessor.HttpContext!.Request.Query["u"];

        var definition = await _featureDefinitionProvider.GetFeatureDefinitionAsync(featureName);
        if (definition.EnabledFor.Any(t => t.Name.In("Microsoft.TimeWindow")))
            return;

        await _cache.HashSetAsync($"tc:{username}", featureName, enabled ? 1 : 0, When.Always);
        await _cache.KeyExpireAsync($"tc:{username}", new TimeSpan(0, 30, 0));
    }
}

Then it can be registered in Startup.cs

services.AddTransient<ISessionManager, RedisSessionManager>();
services.AddTogglyWeb(options =>
    {
        options.AppKey = Configuration["Toggly:AppKey"];
        options.Environment = Configuration["Toggly:Environment"];
    });

services.AddSingleton(cfg =>
{
    IConnectionMultiplexer multiplexer = ConnectionMultiplexer.Connect(Configuration["ConnectionStrings:Redis"]);
    return multiplexer.GetDatabase();
});
PreviousIFeatureManagerSnapshotNextDisabled Action Handling

Last updated 2 years ago

Was this helpful?

🔘