Feature Flags with ConfigCat and Asp.Net Core Options
Tags: aspnetcore, optionspattern, configcat
Published: 2021-10-10 09:40:25

A feature flag or 'feature toggle' is a common technique to enable or disable application features dynamically. For example, feature flags enable product owners to turn on and off features during runtime of the application. Certain features may be turned on/off for given environments, users, or regions only. This way features may be A-B tested, or tested with a given percentage of users, or different countries of the world. It can also provide a solution to meet regional restrictions of different countries.

In this post I investigate integrating [ConfigCat's](https://configcat.com/) feature management with an ASP.NET Core 6 web API service and the `Options<T>` [pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0). I will also focus on using the *AutoPolling* mechanism built into the ConfigCat client library, to refresh feature flags' state during application runtime.

> Throughout my carrier I have used some sort of a feature flag solution. In some cases, this was a conscious decision built upon a well-designed application architecture, while in other cases it was just an `if` statement with a key-value pair in the configuration file. However, I have not yet encountered such a complete service as the one provided by [ConfigCat](https://configcat.com/).

I will focus on using feature flags solution within web services. Although, I see an even bigger need for such a robust solution in desktop applications. Managing the configuration of a few web service instances is inherently simpler to manage hundreds or thousands of desktop applications, which is common in the enterprise world.

## Implementation Options

Feature flags can be leveraged in applications many ways. In the past, when computer networks were less ubiquitous, feature flags were typically implemented as compiler directives. This way a given code path was compiled into the application or remained as commented out section. The advantage of this solution is less branching and smaller code size. Although to 'toggle' a feature a new compilation of the source code is required, which makes this solution less dynamic. Users would need to uninstall/install or upgrade their application with the new binaries to get a feature toggled.

Today the most common technique is branching by `if` statements. If the features flag is in enabled state a certain code path of the application is executed. For example, when a button is clicked to start an order processing, *if* a given feature flag enabled an SMS is also sent to the user. One could express this as:

```csharp
// ...
ProcessOrder();
var isSmsFeatureEneabled = client.GetValue("sendSMS", false);
if(isSmsFeatureEneabled)
    SendSms();
// ...
```

Another approach would be to leverage [branching by abstractions](https://www.martinfowler.com/bliki/BranchByAbstraction.html). As this is a larger topic, I am not detailing it within this post, exploring this area may worth its own writing.

One very recent feature flag I encountered comes from .NET itself: using the [HTTP3 preview feature](https://devblogs.microsoft.com/dotnet/http-3-support-in-dotnet-6/) in `HttpClient` requires the developers to proactively enable the feature by setting the `<EnablePreviewFeatures>True</EnablePreviewFeatures>` flag in the csproj file.

## ConfigCat and Asp.Net Core and .NET 6

Using the ConfigCat's service does not restrict us choosing any of implementation techniques, although one would probably not choose to use compiler directives. In this section I show how one can integrate the ConfigCat's configuration with the `Option<T>` pattern of .NET 6. I am using a late preview version of .NET6 at the time of writing this post. DotNet 6 provides a new configuration concept with `ConfigurationManager` type, which is not available in the previous versions. `ConfigurationManager` allows to initialize configuration sources while using configuration values of previously initialized sources. It achieves this by implementing `IConfigurationBuilder, IConfigurationRoot, IConfiguration` interfaces at the same time.

One consideration to make is that no user specific feature flag will be used, which means that in this implementation flags will not be respected if set for specific users or 'target % users' on the ConfigCat's portal. In all cases the 'To all users' value of the feature flag is used.

In general, when using `Options<T>` pattern, there is no good API to query user specific settings, and in a web application used by multiple users, there is also no effective way to fetch flags for one or a few users during application startup. Thus, all feature flags shall be independent of users when being registered with Options. We can leverage though non-user specific information i.e. semantic version of the application is greater than 1.2.3. I will leave it up for the reader to extend the presented solution with such extensions.

Let me first preview the whole 'startup' code and the action of the service, then describe the necessary types I created for the solution. Here is *Program.cs*:

```csharp
using ConfigCat.Client;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddConfigCat(false);
builder.Services.Configure<FeatureSet>(builder.Configuration.GetSection(nameof(FeatureSet)));
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("api/feature", async (HttpContext context, IOptionsSnapshot<FeatureSet> features) =>
{
    if (features.Value.GrandFeature)
    {
        context.Response.StatusCode = StatusCodes.Status200OK;
        await context.Response.WriteAsync("Hello World!");
    }
    else
    {
        context.Response.StatusCode = StatusCodes.Status404NotFound;
    }
});

app.Run();

public class FeatureSet
{
    public bool GrandFeature { get; set; }
}
```

This web API has a single GET endpoint `api/feature`. The response depends on a feature flag: `GrandFeature`. When the feature is turned on, it returns HTTP 200 OK, with `Hello World!` as the content. When the feature is turned off, it returns 404 Not Found. The state of the feature flag is accessed through `IOptionsSnapshot<FeatureSet> features`, which I will explain later in this post.

The `builder.Configuration.AddConfigCat(false);` uses a custom extension method to add the toggle values of ConfigCat to the Asp.Net Core's configuration.
The second line `builder.Services.Configure<FeatureSet>(builder.Configuration.GetSection(nameof(FeatureSet)));` sets up the feature flags with the `Options<T>` pattern. This is the standard way to bind a given section of the configuration to a type, while also registering the type with the DI container. Here, I bind the configuration to a type called `FeatureSet` which has a single boolean property *GrandFeature*.

Let's investigate the custom extension method. The upcoming code focuses on getting the configuration values of ConfigCat into Asp.Net's configuration. Below the extension method uses `ConfigurationManager` to read the ConfigCat API key and *poll interval* settings from the 'appsettings.json' file. These configuration values are added by Asp.Net web application's file provider during startup. In production, one would prefer to pass the ConfigCat key as a secret or as an environment variable. In either case a configuration source would set the value before `AddConfigCat` is invoked. As '0' is an invalid interval for polling, the extension method validates it. The method has an *optional* parameter which indicates the desired behavior when ConfigCat cannot fetch the feature flags, while *onError* parameter is an action that is invoked in case of an exception. Feature flags are fetched from the service periodically, the *onError* parameter provides a way to observe errors during the background polls.

```csharp
public static class Extensions
{
    public static IConfigurationBuilder AddConfigCat(
        this ConfigurationManager manager,
        bool optional = false,
        Action<Exception>? onError = null)
    {
        var key = manager["ConfigCat:Key"] ?? throw new ArgumentNullException("ConfigCat:Key");
        var pollInterval = manager.GetValue<TimeSpan>("ConfigCat:PollInterval");
        if (pollInterval == TimeSpan.Zero)
            throw new ArgumentNullException("ConfigCat:PollInterval");
        var options = new ConfigCatOptions(key, pollInterval, optional, onError);
        if (manager is IConfigurationBuilder builder)
            builder.Add(new ConfigCatConfigurationSource(options));
        return manager;
    }
}

public record ConfigCatOptions(string Key, TimeSpan RefreshInterval, bool IsOptional, Action<Exception>? OnError);
```

`ConfigCatOptions` record type is encapsulating the parameters for `ConfigCatConfigurationProvider`.

The next type is `ConfigCatConfigurationSource`. An `IConfigurationSource` is required to be implemented as this is the type added to the configuration sources. The responsibility of the type is to create an `IConfigurationProvider`. With .NET6 when a configuration provider is removed or modified, all the remaining sources are rebuilt. This implementation returns a lazily instantiated `ConfigCatConfigurationProvider` instance. I use the singleton semantics because auto polling built into the `ConfigCatConfigurationProvider` refreshes the configuration automatically.

```csharp
public class ConfigCatConfigurationSource : IConfigurationSource
{
    private readonly ConfigCatOptions _options;
    private ConfigCatConfigurationProvider? _provider;

    public ConfigCatConfigurationSource(ConfigCatOptions options)
    {
        _options = options ?? throw new ArgumentNullException(nameof(options));
    }

    public IConfigurationProvider Build(IConfigurationBuilder builder) =>
        _provider ??= new ConfigCatConfigurationProvider(_options);
}
```

> Another use case could be when the feature flags are read only at application startup. In certain applications this could be a valid scenario. For this, manual polling would be a better choice, and creating a new instance of `ConfigCatConfigurationProvider` on every `Build()` method invocation would also make sense.

The last and most complex class to implement is `ConfigCatConfigurationProvider`. This type derives from `ConfigurationProvider` which already implements many of the `IConfigurationProvider` interface members. Here, I only override the `Load()` method, which is invoked by the *Host* right after the configuration provider is instantiated. In the first invocation I create a new `ConfigCatClient`, and because `AutoPollConfiguration` uses a `Timer` to load configuration data asynchronously, a task completion source must be waited. Unfortunately, the method signature does not allow to use the `await` keyword. Without waiting for the task completion source, further providers would not be able to read the data set by this provider.

```csharp
public class ConfigCatConfigurationProvider : ConfigurationProvider, IDisposable
{
    private readonly ConfigCatOptions _options;
    private readonly AutoPollConfiguration _polling;
    private readonly TaskCompletionSource _initialLoad;
    private IConfigCatClient? _configCatClient;

    public ConfigCatConfigurationProvider(ConfigCatOptions options)
    {
        _options = options ?? throw new ArgumentNullException(nameof(options));
        _polling = new AutoPollConfiguration
        {
            SdkKey = _options.Key ?? throw new ArgumentNullException(nameof(_options.Key)),
            PollIntervalSeconds = (uint)_options.RefreshInterval.TotalSeconds,
        };
        _initialLoad = new TaskCompletionSource();
        _polling.OnConfigurationChanged += OnConfigurationChanged;
    }

    private void OnConfigurationChanged(
        object sender,
        OnConfigurationChangedEventArgs eventArgs)
    {
        LoadData();
        _initialLoad.TrySetResult();
    }

    public void Dispose() { }

    public override void Load()
    {
        _configCatClient ??= new ConfigCatClient(_polling);
        _initialLoad.Task.GetAwaiter().GetResult();
    }

    public void LoadData()
    {
        try
        {
            Data = ParseKeys();
        }
        catch (Exception ex)
        {
            if (_options.IsOptional)
                Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            if (_options.OnError is { })
                _options.OnError.Invoke(ex);
            else
                throw;
        }
        OnReload();
    }

    private IDictionary<string, string> ParseKeys()
    {
        if (_configCatClient == null)
            throw new InvalidOperationException(nameof(_configCatClient));

        var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        foreach (var key in _configCatClient.GetAllKeys())
        {
            var value = _configCatClient.GetValue(key, string.Empty);
            result.Add(key.Replace('_', ':'), value);
        }
        return result;
    }
}
```

Once `ConfigCatClient` has loaded the data, the `OnConfigurationChanged` event is fired. This is when all key value pairs are loaded. `LoadData()` and `ParseKeys()` methods read and parse the keys and corresponding values. The result dictionary is set in the *Data* property, which is declared by the base class. The only additional logic applied here is to replace the underscore characters with semicolons. This is done, as the ':' character is unsupported in key names, so to deal with the hierarchy of configuration values, another character must be used for the ConfigCat feature names. Using the '_' character resembles a similar behavior to using configuration values with environment variables.

Note, that `LoadData()` method invokes a method from the base type: `OnReload();`. This will generate a new change token signaling the configuration provider that the configuration values have changed. The values of options might change due to the built-in auto-polling mechanism, however the `OnConfigurationChanged` event is only fired when the values have changed.

To read the latest values of configuration while serving the HTTP request, an `IOptionsSnapshot<FeatureSet>` is passed to the GET request's action handler. This type is useful in scenarios where options should be recomputed on every request.

## Conclusion

In one way or another an *aging*, but still maintained application requires a solution for feature flags. The more robust this solution is the more choice is given to the development team to isolate certain preview features to certain users. Implementing a custom feature flag solution does not usually provide a competitive advantage, using service built for the purpose makes sense. In this regards, ConfigCat's solution seems a reasonable choice for my next project.