Available packages
Maxlona.FeatureFlags
The online .NET SDK: evaluation with typed variants and decision explanations, plus the complete public Management API, over a direct call to Maxlona. Register with a single key and environment; the SDK owns the service URL and HTTP transport. There is only one kind of key — a Management Key — and it can carry one or more permission levels (CanEvaluate, CanRead, CanWrite), so the same key setting can back both the evaluation client and the management client, or you can issue narrower keys per service. Resilient by default: Polly retries with exponential backoff, per-attempt timeouts, plain-language firewall and proxy diagnostics, and its own Serilog file log. Use IFeatureFlags and IFeatureFlagManagement in your application. Prefer Maxlona.FeatureFlags.Offline instead if you want evaluation to keep working, from an encrypted local cache, while Maxlona is unreachable.
Install
dotnet add package Maxlona.FeatureFlags --version 1.4.1A newly published version takes a few minutes to be indexed by NuGet.org. If a version we have just announced is not found yet, wait and retry, or run dotnet restore --no-cache to bypass a stale local index. Previously published versions are unaffected.
Package details
| Package ID | Maxlona.FeatureFlags |
|---|---|
| Latest version | 1.4.1 |
| Published by | Maxlona |
| Target framework | net8.0 — runs on .NET 8, 9, and 10 |
| Dependencies | Microsoft.Extensions.Http/Hosting 8.0.1, Polly.Core 8.5.2, Serilog 4.2.0 |
| License | Maxlona Proprietary SDK License, all rights reserved. |
Evaluate a flag
using Maxlona.FeatureFlags;
using Maxlona.FeatureFlags.Models;
using Microsoft.Extensions.DependencyInjection;
// A Management Key with CanEvaluate permission is enough for this client.
var services = new ServiceCollection();
services.AddMaxlonaFeatureFlags(
Environment.GetEnvironmentVariable("MAXLONA_MANAGEMENT_KEY")!, "production");
services.AddTransient<CheckoutService>();
using var provider = services.BuildServiceProvider();
var checkout = provider.GetRequiredService<CheckoutService>();
Console.WriteLine(await checkout.UseNewCheckoutAsync("user-123", "pro"));
// Inject the service into your application. No URL or HTTP client setup.
public sealed class CheckoutService(IFeatureFlags flags)
{
public Task<bool> UseNewCheckoutAsync(
string userId, string plan, CancellationToken ct = default) =>
flags.IsEnabledAsync("checkout-redesign", new EvaluationContext
{
UserId = userId,
Attributes = new Dictionary<string, object?> { ["plan"] = plan }
}, defaultValue: false, cancellationToken: ct);
}Using the SDK without dependency injection
Create a FeatureFlags service with your key and environment. The SDK handles the endpoint, authentication, retries, and transport. Logging works by default; the directory override below is optional. Use FeatureFlagManagement with a Management Key for administration.
using Maxlona.FeatureFlags;
using Maxlona.FeatureFlags.Models;
// Reuse for the application lifetime; dispose at shutdown. There is no
// service URL to configure — set options.BaseUri only for a proxy or a
// private deployment.
using var flags = new FeatureFlags(
Environment.GetEnvironmentVariable("MAXLONA_MANAGEMENT_KEY")!, "production",
options => options.Logging.Directory = "logs/flags"); // optional
var context = new EvaluationContext { UserId = "user-123" };
var enabled = await flags.IsEnabledAsync("checkout-redesign", context, defaultValue: false);
var limit = await flags.GetValueAsync<int>("checkout-limit", context, defaultValue: 10);
var attempt = await flags.TryEvaluateAsync("checkout-redesign", context, explain: true);
Console.WriteLine(attempt.Result?.Reason?.Message ?? attempt.Error?.Message);
// FeatureFlagManagement uses the same key when it carries CanRead/CanWrite,
// or a separate, narrower key when you want to keep evaluation and
// administration apart.
using var management = new FeatureFlagManagement(
Environment.GetEnvironmentVariable("MAXLONA_MANAGEMENT_KEY")!);
var report = await management.CheckConnectivityAsync();
Console.WriteLine($"healthy: {report.IsHealthy} — {report.Summary}");Managing flags from code (.NET)
There is only one kind of key — a Management Key — and it can carry one or more permission levels: CanEvaluate, CanRead, CanWrite. Use the same key for both clients when it carries every permission you need, or issue a narrower key per service so neither carries more authority than the code path using it.
Download the .NET consumer sample
A complete dependency-injection console app built with the working, published Maxlona.FeatureFlags 1.4.1 NuGet package. Every sample is a small class taking the Maxlona services it needs through its constructor: evaluation, typed variants, graceful degradation, reading and administering flags through the Management API, and the same API with no container at all.
Create a flag and roll it out
using Maxlona.FeatureFlags;
using Maxlona.FeatureFlags.Models;
using Microsoft.Extensions.DependencyInjection;
// Use a Management Key with CanWrite permission for these operations.
var services = new ServiceCollection();
services.AddMaxlonaFeatureFlagManagement(
Environment.GetEnvironmentVariable("MAXLONA_MANAGEMENT_KEY")!);
using var provider = services.BuildServiceProvider();
var management = provider.GetRequiredService<IFeatureFlagManagement>();
var created = await management.CreateAsync(new CreateFeatureFlagRequest
{
Name = "checkout-redesign",
Project = "Main App",
Type = "release",
Description = "New checkout flow",
Variants =
[
FlagVariant.Create("on", true, "Feature enabled"),
FlagVariant.Create("off", false, "Feature disabled")
]
});
// All users reach targeting rules; pro users receive the on variant.
// Users who match no rule receive the default off variant.
await management.UpsertConfigurationAsync(created.Name, new UpsertFlagConfigurationRequest
{
Stage = "production",
Enabled = true,
DefaultVariantKey = "off",
RolloutPercentage = 100,
Rules =
[
new TargetingRule
{
Id = "pro-users",
Description = "Pro plan customers",
Priority = 10,
Conditions = [TargetingCondition.Create("plan", "==", "pro")],
Allocations = [new VariantAllocation { VariantKey = "on", Percentage = 100 }]
}
]
});
// Read or replace the environments this flag uses. A change that would leave a
// dependency without a matching environment is rejected with a 409 unless you
// set DependencyResolution to "align" or "remove_dependencies".
var current = await management.GetEnvironmentsAsync(created.Name);
var updated = await management.UpdateEnvironmentsAsync(created.Name, new UpdateFlagEnvironmentsRequest
{
Environments = ["dev", "production"]
});
Console.WriteLine($"{string.Join(", ", current.Environments)} -> {string.Join(", ", updated.Environments)}");.NET API surface
IFeatureFlags
| Method | Returns | Notes |
|---|---|---|
| EvaluateAsync(flagKey, context, explain, ct) | EvaluationResult | Full result. Pass explain: true for the decision reason. Throws if unreachable. |
| TryEvaluateAsync(flagKey, context, explain, ct) | EvaluationAttempt | Returns the result or service failure. Caller cancellation and invalid arguments still throw. |
| IsEnabledAsync(flagKey, context, ct) | bool | True when the variant key is neither "off" nor a false/null value. |
| IsEnabledAsync(flagKey, context, defaultValue, ct) | bool | Falls back to your default when the service is unreachable or refuses. |
| GetValueAsync<T>(flagKey, context, ct) | T? | Deserializes the variant value into your own type. |
| GetValueAsync<T>(flagKey, context, defaultValue, ct) | T? | Falls back to your default on failure or a type mismatch. |
IFeatureFlagManagement
| Method | Returns | Permission |
|---|---|---|
| ListAsync(ct) | IReadOnlyList<FeatureFlag> | CanRead |
| GetAsync(name, ct) | FeatureFlag | CanRead |
| TryGetAsync(name, ct) | FeatureFlag? | CanRead |
| ExistsAsync(name, ct) | bool | CanRead |
| GetEnvironmentsAsync(name, ct) | FlagEnvironments | CanRead |
| CreateAsync(request, ct) | FeatureFlag | CanWrite |
| UpdateAsync(name, request, ct) | FeatureFlag | CanWrite |
| UpdateEnvironmentsAsync(name, request, ct) | FlagEnvironments | CanWrite |
| ArchiveAsync(name, ct) | FeatureFlag | CanWrite |
| RestoreAsync(name, ct) | FeatureFlag | CanWrite |
| DeleteAsync(name, ct) | void | CanWrite |
| UpsertConfigurationAsync(name, request, ct) | FlagConfiguration | CanWrite |
| CheckConnectivityAsync(ct) | ConnectivityReport | CanRead |
Retries and timeouts
Every call runs through a Polly pipeline: three retries by default with exponential backoff and jitter, plus a per-attempt timeout of ten seconds. A 429's Retry-After is honoured, capped by Retry.MaxDelay. All of it applies whether the client came from dependency injection or was constructed by hand.
| Failure | Reads, updates, deletes, upserts, evaluations | CreateAsync |
|---|---|---|
| Connection refused, DNS failure, host unreachable | Retried | Retried — nothing was delivered |
| 408, 429, 503 | Retried | Retried — the server declined it |
| 500, 502, 504 | Retried | Not retried — the write may have landed |
| Connection reset mid-flight, timeout | Retried | Not retried |
| 400, 401, 403, 404, 409 | Never retried | Never retried |
Degrading gracefully
A feature flag is a configuration lookup, and an outage in a configuration lookup should not take down the feature it configures. Pass a fallback and an unreachable Maxlona stops being an exception: the failure is logged and your default wins. The overloads without a fallback still throw, for callers that want to know.
Errors and diagnostics
An API that answered and refused throws FeatureFlagApiException, carrying StatusCode, ErrorCode, TraceId, ResponseBody, and RetryAfter. An API that could not be reached at all throws FeatureFlagConnectionException instead, with a classified Problem and a plain-language Guidance string you can paste into a network ticket.
The distinction matters because a corporate network breaks an SDK in ways that all look identical from .NET — every one arrives as the same opaque HttpRequestException.
| Problem | Detected from | What to check |
|---|---|---|
| DnsFailure | The host name did not resolve | Check DNS from the machine and look for a resolver that blocks unknown domains. |
| BlockedByFirewall | Connection refused, reset, unreachable, or silently dropped | Allow outbound HTTPS to the host and port in the host firewall, the network ACL, and any NSG or security group. Set HTTPS_PROXY if a proxy is in use. |
| TlsInterception | The TLS handshake was rejected | A TLS-inspecting proxy is re-signing traffic. Trust its root certificate, or bypass maxlona.com from inspection. |
| ProxyAuthenticationRequired | HTTP 407 | A proxy wants credentials. Maxlona itself never returns 407. |
| InterceptedResponse | A non-JSON body on a 2xx | A captive portal or web filter answered instead of Maxlona. |
| Timeout | The attempt timeout elapsed | Nothing answered — the signature of a firewall that drops rather than refuses. |
Call CheckConnectivityAsync at startup to test the whole path without throwing. A report that is unhealthy with no Problem means the network is fine and the API refused — a revoked key, a scope mismatch, or an inactive subscription. A Problem means the traffic never got there.
Local logging
The SDK keeps its own Serilog file log, on by default, recording every retry, every classified failure, and the guidance above. It never touches Serilog.Log.Logger, so switching it on cannot disturb your application's own logging — or set Logging.Logger to fold the SDK's events into it deliberately.
Files land in a maxlona-FFlags folder beside your running application (AppContext.BaseDirectory), rolling daily and keeping seven files of up to 16 MB. Set Logging.Directory to choose an absolute path or a relative path such as logs/flags. Relative paths resolve beside the executable. Set Logging.Enabled = false to disable file logging. An unwritable directory skips logging without redirecting it elsewhere.
Using another language?
Download the HTTP Postman collection to learn how to evaluate feature flags through standard HTTPS GET and POST requests. This approach works with Node.js, React, Angular, .NET, Python, Java, PHP, Kotlin, Swift, Go, and any other platform that can send an HTTP request. The collection includes the endpoints, request payloads, authentication header, and response shapes you need to reproduce the calls in your application. For browser applications such as React or Angular, send the request through your backend so the Management Key is never exposed in client-side code.