.NET client
Package: Nona.Client
OpenFeature provider package: Nona.OpenFeature.Provider
Targets:
netstandard2.0net8.0
The .NET client is a good fit for:
- ASP.NET applications
- worker services
- console tools
- backend services that want a direct typed integration
Install
Section titled “Install”dotnet add package Nona.ClientPrepare the value in admin
Section titled “Prepare the value in admin”Before wiring the backend:
- open
Projects - open the backend service project
- select the target environment such as
production - create the parameter or flag you want to read
- create an API key in the
API Keyssection - use
serverscope for backend-only reads whenever possible
For a first backend test, create Features:UseLegacySearch as a boolean entry or App:Settings as a JSON entry.
Prepare the value with the CLI
Section titled “Prepare the value with the CLI”nona entries set \ --project storefront \ --environment production \ --key Features:UseLegacySearch \ --value false \ --scope server \ --content-type boolean
nona keys create \ --project storefront \ --name "API service" \ --scope server \ --environment productionThe default client reads working parameters, so no release is required for this example. Publish a release only when you enable release reads; set it active if you omit ReleaseVersion.
Read a string
Section titled “Read a string”using Nona.Client;
var client = new NonaClient( "https://nona.example.com", "production", apiKey: Environment.GetEnvironmentVariable("NONA_API_KEY"));
var checkout = await client.GetStringValueAsync("Features:Checkout");var checkoutEnabled = checkout == "true";For feature flags, you will often prefer boolean entries and either metadata inspection or OpenFeature depending on how your application is structured.
Read a boolean flag cleanly
Section titled “Read a boolean flag cleanly”var checkout = await client.GetConfigValueAsync("Features:UseLegacySearch");var useLegacySearch = checkout.ContentType == "boolean" && string.Equals(checkout.Value, "true", StringComparison.OrdinalIgnoreCase);That keeps the application aligned with the logical type stored in Nona.
Read value metadata
Section titled “Read value metadata”var value = await client.GetConfigValueAsync("Features:Checkout");
Console.WriteLine(value.Value);Console.WriteLine(value.ContentType);ContentType is one of text, number, boolean, or json.
This is useful when one service reads mixed config and needs to branch on the value type.
Return null for missing keys
Section titled “Return null for missing keys”var value = await client.TryGetConfigValueAsync("Missing:Key");
if (value is null){ Console.WriteLine("Key was not found");}This is helpful for optional settings or for gradual rollout of new keys across environments.
Pin a release version
Section titled “Pin a release version”By default, reads use working parameters. Set UseReleases when constructing the client to read immutable release snapshots instead.
Set ReleaseVersion to pin the client to an exact release or release line:
var client = new NonaClient(new NonaClientOptions{ BaseAddress = new Uri("https://nona.example.com"), EnvironmentId = "production", ApiKey = Environment.GetEnvironmentVariable("NONA_API_KEY"), UseReleases = true, ReleaseVersion = "1.1.x"});Use an exact version such as 1.1.0 for a fixed snapshot. Use a line such as 1.1.x to read the highest patch in that line.
Omit ReleaseVersion while keeping UseReleases = true to follow the environment’s active release. If no active release exists, reads fail with 409 and ErrorCode == "active_release_not_configured"; they never fall back to working parameters. Source and selector are fixed for the client’s lifetime, so construct another client to use a different source or selector. When UseReleases is false (the default), ReleaseVersion is normalized and retained but ignored for requests and cache identity.
Fetch all values or a prefix group
Section titled “Fetch all values or a prefix group”Fetch every readable value at startup, or restrict the snapshot to keys that begin with a prefix:
IReadOnlyDictionary<string, NonaConfigValue> all = await client.GetAllValuesAsync();IReadOnlyDictionary<string, NonaConfigValue> features = await client.GetAllValuesAsync("Features:");Prefixes may contain ASCII letters, digits, colons, dots, underscores, and dashes, and matching is case-insensitive. Pass null or an empty string for an unfiltered snapshot. Any other character produces NonaClientException with StatusCode == HttpStatusCode.BadRequest; failed responses are not cached. Bulk reads cache an ETag independently for each release and normalized prefix, reuse the snapshot after 304 Not Modified, and prime single-key reads only for returned keys. The snapshots share the configured memory/LRU budget and returned dictionaries are defensive copies.
Available methods
Section titled “Available methods”GetAllValuesAsync(string? prefix = null, CancellationToken cancellationToken = default)GetConfigValueAsync(string key, CancellationToken cancellationToken = default)TryGetConfigValueAsync(string key, CancellationToken cancellationToken = default)GetStringValueAsync(string key, CancellationToken cancellationToken = default)GetJsonValueAsync<T>(string key, JsonTypeInfo<T> jsonTypeInfo, CancellationToken cancellationToken = default)
Read JSON
Section titled “Read JSON”GetJsonValueAsync<T> uses source-generated JsonTypeInfo<T>.
using System.Text.Json.Serialization;using Nona.Client;
var settings = await client.GetJsonValueAsync( "App:Settings", AppJsonContext.Default.AppSettings);
public sealed record AppSettings(string Region, int MaxItems);
[JsonSerializable(typeof(AppSettings))]internal partial class AppJsonContext : JsonSerializerContext{}JSON works well when a backend service consumes a cluster of related settings together.
Example Nona setup:
- key:
App:Settings - content type:
json - scope:
server
Default cache
Section titled “Default cache”The .NET client caches values in memory by default for 30 seconds. CacheTtl changes the cache lifetime and must be greater than zero; CacheMemoryLimitMegabytes defaults to 5 and must also be greater than zero. The current client does not expose a cache-disable option.
Set AllowStaleCache to return an expired cached value while the client refreshes it in the background.
var client = new NonaClient(new NonaClientOptions{ BaseAddress = new Uri("https://nona.example.com"), EnvironmentId = "production", ApiKey = Environment.GetEnvironmentVariable("NONA_API_KEY"), CacheTtl = TimeSpan.FromSeconds(30), CacheMemoryLimitMegabytes = 5, AllowStaleCache = true});Use AllowStaleCache carefully. It can improve resilience and smooth over transient failures, but it also means the application may temporarily serve an older value while refresh happens in the background.
Basic troubleshooting
Section titled “Basic troubleshooting”If a .NET read fails:
- confirm the
EnvironmentIdmatches the Nona environment name - confirm that
UseReleasesselects the intended source - in release mode, confirm the expected release is active or configure
ReleaseVersion - confirm the API key belongs to the same project as the entry
- confirm the key scope can read the entry scope
- try the same entry once with HTTP to separate transport issues from application code
Good first backend flow
Section titled “Good first backend flow”A practical first integration looks like this:
- read one operational flag at startup or request time
- change it once in admin
- confirm the service sees the change
- tune
CacheTtlonly after the read path is working - move to OpenFeature when the service becomes flag-oriented
When to use the .NET client
Section titled “When to use the .NET client”Use the .NET client when you want:
- a direct Nona integration in C#
- built-in cache behavior
- typed JSON reads
- a simpler path than building your own HTTP wrapper
Use HTTP instead when you only need a minimal raw request path.
OpenFeature provider
Section titled “OpenFeature provider”Install the optional provider package:
dotnet add package Nona.OpenFeature.Providerusing Nona.Client;using Nona.OpenFeature.Provider;using OpenFeature;
var nona = new NonaClient( "https://nona.example.com", "production", apiKey: Environment.GetEnvironmentVariable("NONA_API_KEY"));
const string domain = "nona-production";
await Api.Instance.SetProviderAsync( domain, new NonaOpenFeatureProvider(nona));
var featureClient = Api.Instance.GetClient(domain);var enabled = await featureClient.GetBooleanValueAsync("Features:Checkout", false);If your team wants a more flag-oriented, vendor-neutral integration surface, continue with OpenFeature.