ConfigurationStore.Client 0.1.4-prerelease

This is a prerelease version of ConfigurationStore.Client.
The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package ConfigurationStore.Client --version 0.1.4-prerelease
                    
NuGet\Install-Package ConfigurationStore.Client -Version 0.1.4-prerelease
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ConfigurationStore.Client" Version="0.1.4-prerelease" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ConfigurationStore.Client" Version="0.1.4-prerelease" />
                    
Directory.Packages.props
<PackageReference Include="ConfigurationStore.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ConfigurationStore.Client --version 0.1.4-prerelease
                    
#r "nuget: ConfigurationStore.Client, 0.1.4-prerelease"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ConfigurationStore.Client@0.1.4-prerelease
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ConfigurationStore.Client&version=0.1.4-prerelease&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ConfigurationStore.Client&version=0.1.4-prerelease&prerelease
                    
Install as a Cake Tool

ConfigurationStore.Client

.NET client for ConfigurationStore, a self-hosted secrets and configuration manager. It adds a ConfigurationStore server as an IConfiguration source, so secrets arrive as ordinary configuration values, feature flags land under the FeatureManagement section, and files are downloaded on demand. When push is enabled the client keeps a live connection and reloads configuration when it changes on the server.

Install

dotnet add package ConfigurationStore.Client --prerelease

Quick start

Add a ConfigurationStore section with the server URL and a per-environment API key (put the key in user secrets or an environment variable, not in appsettings.json):

{
  "ConfigurationStore": {
    "ServerUrl": "https://config.example.com",
    "ApiKey": "cfgs_...",
    "ClientName": "orders-api",
    "EnablePush": true
  }
}

Then wire it up with one call on the host builder:

var builder = WebApplication.CreateBuilder(args);

builder.AddConfigurationStore();

This reads the ConfigurationStore section, registers the configuration source, and registers a singleton ConfigurationStoreClient in DI. The server is contacted at startup: like a database-backed app needs its database, the app needs the server to be reachable to start.

Every option binds from the section, so it can just as well come from environment variables — ConfigurationStore__ServerUrl, ConfigurationStore__ApiKey, ConfigurationStore__ClientName, ConfigurationStore__EnablePush — which is handy in containers. ClientName (default: the application name) is how this instance shows up in the server's connected-clients list; set it to tell one consumer apart from another. Anything you set through the AddConfigurationStore(options => …) callback overrides the configured values.

Secrets

Secrets are plain configuration values, so read them however you already read configuration — IConfiguration, or bind an options class with IOptions<T> / IOptionsMonitor<T>:

string connectionString = builder.Configuration["Database:ConnectionString"];

builder.Services.Configure<DatabaseOptions>(builder.Configuration.GetSection("Database"));

With push enabled, the provider raises change tokens on updates, so IOptionsMonitor<T> re-binds live.

Feature flags

Feature flags map to the FeatureManagement:{name} section, so they work with Microsoft.FeatureManagement out of the box:

builder.Services.AddFeatureManagement();

// elsewhere
if (await featureManager.IsEnabledAsync("NewCheckout"))
{
    // ...
}

Flag changes reload live too, so evaluation reflects the current server state.

Files

Files are fetched on demand through the injected ConfigurationStoreClient:

public sealed class CertificateLoader(ConfigurationStoreClient configStore)
{
    public async Task<byte[]?> LoadAsync()
    {
        ConfigurationStoreFile? file = await configStore.GetFileAsync("web-cert");
        return file?.Content;
    }
}

configStore.Files lists the file entries (key, file name, version) from the latest snapshot.

Reacting to changes

Beyond the automatic configuration reload, you can subscribe to change notifications. Both return an IDisposable; dispose it to unsubscribe.

IDisposable configSub = configStore.OnConfigChanged(() =>
{
    // a secret or feature flag changed
});

IDisposable fileSub = configStore.OnFileChanged(file =>
{
    // this file changed on the server; re-download if you cache it
});

Set EnablePush to false to disable the live connection and use only the snapshot taken at startup.

Resilience and caching

The push connection reconnects automatically and indefinitely (capped backoff with jitter), so it survives a server redeploy rather than giving up after a short window. When it reconnects it re-fetches the snapshot, so any changes pushed while the client was disconnected are still picked up.

By default the client is in-memory only and the server must be reachable at startup — if the snapshot cannot be fetched, startup fails (like a database-backed app needs its database). This is the safest default: nothing is written to disk.

Opt-in on-disk caching

Enable caching so the app can start from a cached snapshot when the server is unreachable at startup. Because the cache holds secrets it is always encrypted, so you must supply both a store and a protector:

builder.AddConfigurationStore(options =>
{
    options.Cache = new ConfigurationStoreCacheOptions
    {
        Store = new FileCacheStore(),           // per-user directory by default
        Protector = new DpapiCacheProtector(),  // Windows; use AesGcmCacheProtector elsewhere
        MaxCacheAge = TimeSpan.FromHours(24),   // older entries are ignored (default 24h)
    };

    options.InitialFetchTimeout = TimeSpan.FromSeconds(5); // startup wait before falling back to cache
});

Startup behaviour with caching enabled:

At startup Result
Server reachable Normal start; snapshot (and downloaded files) written to the cache
Server unreachable, usable cache present Start from the cache (degraded); the connection keeps retrying and resyncs when the server returns
Server unreachable, no usable cache (first run, cleared, too old, corrupt) Startup fails
API key rejected (401/403) Startup fails immediately — a rejected key is a misconfiguration, never a cache fallback

Downloaded files are cached on demand the same way: GetFileAsync returns the cached copy when the server is unreachable (keyed by the file's version, so a changed file is re-fetched), and throws if no cached copy exists.

Configuring the cache from configuration / environment

Caching can also be turned on entirely from configuration — no code — which is what you want in a container. Set Cache:Enabled to true and provide the directory, protector and (for AES-GCM) key. When Cache:Enabled is absent or false, every other Cache:* value is ignored and caching stays off; when it is true, startup fails fast if the configuration is incomplete rather than silently degrading.

{
  "ConfigurationStore": {
    "ServerUrl": "https://config.example.com",
    "ApiKey": "cfgs_...",
    "EnablePush": true,
    "Cache": {
      "Enabled": true,
      "Directory": "/data/cs-cache",
      "Protector": "AesGcm",           // AesGcm (default) or Dpapi (Windows only)
      "Key": "<base64 32-byte key>",   // required for AesGcm
      "MaxCacheAge": "12:00:00"        // optional, default 24h
    }
  }
}

The same via environment variables (e.g. a docker-compose environment: block) — no key file, since this must be available before and without a reachable server:

services:
  app:
    image: myapp
    environment:
      ConfigurationStore__ServerUrl: "https://config.example.com"
      ConfigurationStore__ApiKey: "cfgs_..."
      ConfigurationStore__Cache__Enabled: "true"
      ConfigurationStore__Cache__Directory: "/data/cs-cache"
      ConfigurationStore__Cache__Protector: "AesGcm"
      ConfigurationStore__Cache__Key: "<base64 32-byte key>"

The defaults are the same on Windows, macOS and Linux: Protector defaults to AesGcm, which requires a 32-byte Key. Dpapi is available as an override but only on Windows. Note the AES-GCM key sits in the environment next to ApiKey — that is not a new exposure, since the ApiKey already grants access to every value the server returns; the key only protects the on-disk cache against theft without the environment.

Protectors

  • DpapiCacheProtector (Windows) — the encryption key is managed by the OS and bound to the Windows user account, so there is nothing to provision. The strongest option on Windows.
  • AesGcmCacheProtector(key) — portable AES-256-GCM with a 32-byte key you supply from wherever the app can reach it at startup without the server (environment variable, KMS, user secrets). FromBase64Key is a convenience for a base64-encoded key.

Both use authenticated encryption, so a tampered cache entry fails to decrypt and is treated as absent. Implement ICacheStore / ICacheProtector to plug in your own storage or key management.

What at-rest encryption does and does not protect

Because the app must decrypt the cache unattended, the key is reachable by the app. At-rest encryption therefore protects against file-level disclosure — disk theft, backups, other users on the machine, accidental exposure in logs or copies — but not against an attacker who can already run as the application (they can read the same secrets from process memory). Enabling the disk cache is a deliberate availability-vs-secrets-at-rest trade; the default (no disk cache, fail-fast) avoids it entirely.

Reading configuration from a script

The same resolved configuration is available over the HTTP retrieval API, so anything that can make an HTTP request can read it with a per-environment API key — no .NET client required. For example, from PowerShell:

$server = 'https://config.example.com'
$headers = @{ 'X-Api-Key' = $env:CONFIGURATIONSTORE_API_KEY }

# The resolved snapshot: secrets/settings under .strings, feature flags under .flags,
# file entries (key, fileName, version) under .files.
$config = Invoke-RestMethod -Uri "$server/api/config" -Headers $headers

# Keys can contain ':', so quote them when they do.
$connectionString = $config.strings.'Database:ConnectionString'
$newCheckoutEnabled = $config.flags.NewCheckout   # $true / $false

# Download a file entry by key.
Invoke-WebRequest -Uri "$server/api/config/files/web-cert" -Headers $headers -OutFile 'web-cert.pfx'

dotenv output

Add ?format=dotenv to get the snapshot as KEY=value lines (text/plain) instead of JSON, so a script can write an .env file directly — useful for feeding a docker-compose app you cannot modify, with no jq:

curl -s -H "X-Api-Key: $CONFIGURATIONSTORE_API_KEY" \
  "https://config.example.com/api/config?format=dotenv" > .env
docker compose up -d

Format details:

  • Keys: hierarchical : separators are written as __ (e.g. Database:ConnectionString becomes Database__ConnectionString), which is a valid environment-variable name and is mapped back to : by .NET's environment-variable configuration provider. Keys are sorted for stable output.
  • Values use standard dotenv quoting: simple values are emitted bare, and values containing spaces or other special characters are double-quoted with \\, \", \n, \r and \t escapes. An empty value is written as KEY="".
  • Feature flags are written flat as KEY=true / KEY=false. Use ?format=dotenv-net instead to place them under the FeatureManagement__ section so Microsoft.FeatureManagement reads them straight from the environment.
  • Files carry no inline value (their content is fetched separately via /api/config/files/{key}); they are listed as trailing # file: <key> (<fileName>) comments so you know they exist.

Notes

  • No disk cache by default. The client is in-memory only unless you opt in to caching (see Resilience and caching); otherwise the server must be reachable at startup.
  • Logging. The client is created during configuration bootstrap, before the DI logging pipeline exists, so pass an ILoggerFactory via options.LoggerFactory to capture connection, reconnect and cache diagnostics.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated