OpenFeature .NET Provider
Introduction
The OpenFeature .NET Provider is intended to be used in combination with the OpenFeature .NET SDK. The provider wraps the ConfigDirector .NET SDK.
The provider targets net8.0 and netstandard2.0, so it runs on .NET 8 and later as well as on .NET Framework 4.6.2 and later. It requires version 2.14.1 or later of the OpenFeature .NET SDK.
The provider is thread safe. Register one instance when your application starts, and shut OpenFeature down when it stops. Evaluations read config state the provider already holds in memory, so they make no network calls on the request path.
In an ASP.NET Core application, the OpenFeature.Hosting package does that wiring for you. Everything on this page applies either way, see ASP.NET Core further below.
Installation
The provider can be installed from NuGet: https://www.nuget.org/packages/ConfigDirector.OpenFeature.ServerProvider
The OpenFeature .NET SDK (OpenFeature) and the ConfigDirector .NET SDK (ConfigDirector.ServerSdk) are included as dependencies.
dotnet add package ConfigDirector.OpenFeature.ServerProvider --version 1.1.0
<ItemGroup>
<PackageReference Include="ConfigDirector.OpenFeature.ServerProvider" Version="1.1.0" />
</ItemGroup>
Configure and initialize the client
- Create an instance of the provider using your server SDK key. You can retrieve a server SDK key for each environment under
SDK Keysin the dashboard's navigation panel. - Set the OpenFeature provider.
- Get a client instance from OpenFeature.
using ConfigDirector.OpenFeature;
using OpenFeature;
// IMPORTANT: Do not commit the server SDK key to your source code, it is a secret value.
// Waits until the provider is initialized or it times out.
// If initialization times out, the provider will continue attempting to
// initialize in the background.
await Api.Instance.SetProviderAsync(new ConfigDirectorProvider("YOUR-SERVER-SDK-KEY"));
var client = Api.Instance.GetClient();
SetProviderAsync completes when the initial config state arrives or the configured timeout elapses, and it does not throw on a connection failure. Until config state arrives, evaluations return the default value with the PROVIDER_NOT_READY error code, and the provider continues to connect in the background. Check client.ProviderStatus to find out whether config state actually arrived:
using OpenFeature.Constant;
var ready = client.ProviderStatus == ProviderStatus.Ready;
Additional configuration options
Additional configuration options can be passed into the provider in the optional second argument of the constructor, which takes the same ConfigDirectorClientOptions the .NET SDK client does.
For example, the Metadata can be provided like this:
using ConfigDirector;
using ConfigDirector.OpenFeature;
var provider = new ConfigDirectorProvider("YOUR-SERVER-SDK-KEY", new ConfigDirectorClientOptions
{
Metadata = new Metadata { AppName = "YOUR-APP-NAME", AppVersion = "1.0.2" },
});
The provider accepts the same Metadata, LoggerFactory, Connection, and Telemetry options as the .NET SDK client, refer to the additional configuration options section of the .NET SDK for a full list.
Shut down
Shutting OpenFeature down closes the provider, which closes its connections and reports any pending telemetry:
await Api.Instance.ShutdownAsync();
Retrieve config values
To retrieve config values, use the OpenFeature client:
var booleanValue = await client.GetBooleanValueAsync("my-config-key", false);
var stringValue = await client.GetStringValueAsync("my-string-config-key", "Default");
Each OpenFeature getter maps to a ConfigDirector config type:
| OpenFeature getter | ConfigDirector config value |
|---|---|
GetBooleanValueAsync | Boolean |
GetStringValueAsync | String or enum |
GetIntegerValueAsync, GetDoubleValueAsync | Number |
GetObjectValueAsync | JSON object or JSON array |
GetObjectValueAsync returns the config's JSON as an OpenFeature Value, whatever shape it is: a Structure for a JSON object or a list for a JSON array, with nested values inside. Numbers inside it are doubles, as OpenFeature represents every number. The default value is returned only when the config cannot be read.
using OpenFeature.Model;
var settings = await client.GetObjectValueAsync("my-json-config-key", new Value(Structure.Empty));
var theme = settings.AsStructure?.GetValue("theme").AsString;
For additional information regarding the OpenFeature client refer to the OpenFeature .NET SDK documentation.
Evaluation details
The detailed getters of the OpenFeature client, such as GetBooleanDetailsAsync, report why an evaluation produced the value that it did:
| Outcome | Reason | Error code |
|---|---|---|
| A value was found | TARGETING_MATCH | |
| The config carries no value | DEFAULT | |
| The config key is unknown | ERROR | FLAG_NOT_FOUND |
| No config state has arrived yet | ERROR | PROVIDER_NOT_READY |
| The value does not match the requested type | ERROR | TYPE_MISMATCH |
| The provider has been shut down | ERROR | PROVIDER_FATAL |
When a value was found, the Variant is ConfigDirector's identifier for that value. In every other case the default value is returned.
User context
The user context can be provided as the third argument to value getter methods of the OpenFeature client. The OpenFeature .NET provider evaluates targeting rules locally without additional network calls for different contexts.
using OpenFeature.Model;
var context = EvaluationContext.Builder()
.SetTargetingKey("12345") // In OpenFeature, the targeting key represents the context's user ID
.Set("name", "Example User")
// Any arbitrary traits which can be referenced in targeting rules
.Set("traits", Structure.Builder().Set("region", "North America").Build())
.Build();
var booleanValue = await client.GetBooleanValueAsync("my-config-key", false, context);
The evaluation context maps onto the ConfigDirector user context as follows:
| OpenFeature evaluation context | ConfigDirector user context |
|---|---|
The targeting key, or otherwise an id attribute | Id |
The name attribute | Name |
The traits structure attribute | Traits |
The boolean anonymous attribute | Anonymous |
For additional information regarding the OpenFeature client refer to the OpenFeature .NET SDK documentation.
Events
The provider emits PROVIDER_CONFIGURATION_CHANGED whenever configs are updated on the dashboard or via the admin API, carrying the keys of the configs in the update:
using OpenFeature;
using OpenFeature.Constant;
Api.Instance.AddHandler(
ProviderEventTypes.ProviderConfigurationChanged,
payload => Console.WriteLine($"Configs updated: {string.Join(", ", payload?.FlagsChanged ?? [])}"));
It emits PROVIDER_READY when the initial config state arrives after SetProviderAsync has already returned.
ASP.NET Core
The OpenFeature.Hosting package registers OpenFeature with the host. It initializes the provider as the host starts, before the server begins listening, so no request is served config defaults while the first config state is still in flight, and it shuts the provider down when the host stops, which closes the ConfigDirector client.
dotnet add package OpenFeature.Hosting
Register the provider through the OpenFeature builder, and take IFeatureClient from the container wherever a config is read:
using ConfigDirector;
using ConfigDirector.OpenFeature;
using OpenFeature;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenFeature(openFeature =>
openFeature.AddProvider(services => new ConfigDirectorProvider(
builder.Configuration["ConfigDirector:ServerSdkKey"]!,
new ConfigDirectorClientOptions
{
LoggerFactory = services.GetRequiredService<ILoggerFactory>(),
})));
var app = builder.Build();
app.MapGet("/checkout", async (IFeatureClient client) =>
await client.GetBooleanValueAsync("new-checkout", false));
app.Run();
AddOpenFeature drives its own Api instance rather than the static Api.Instance, so the provider registered this way is not reachable through Api.Instance. Take IFeatureClient, and Api if you need it, from the container instead.Event handlers are registered on the same builder. They attach once the provider has initialized, so they see the updates that follow startup:
using OpenFeature.Constant;
builder.Services.AddOpenFeature(openFeature =>
{
openFeature.AddProvider(_ => new ConfigDirectorProvider(builder.Configuration["ConfigDirector:ServerSdkKey"]!));
openFeature.AddHandler(ProviderEventTypes.ProviderConfigurationChanged, services =>
{
var logger = services.GetRequiredService<ILoggerFactory>().CreateLogger("ConfigDirector");
return payload => logger.LogInformation("Configs updated: {Keys}", payload?.FlagsChanged);
});
});