Skip to main content

Blazor SDK

Native Razor feature gates for static SSR, Interactive Server, WebAssembly and Interactive Auto on .NET 8 or later. Can be used with Toggly or with deliberate offline defaults.

Grab the printable Blazor cheat sheet (download PDF) — SSR, Interactive Server, WebAssembly, Auto.

Install and choose a host​

HostPackage / registrationEvaluation scope
Static SSRToggly.FeatureManagement.Blazor.Server, AddTogglyBlazorServer()HTTP request
Interactive ServerSame server packageCircuit, refreshed on reconnect
WebAssemblyToggly.FeatureManagement.Blazor, AddTogglyBlazorWebAssembly(...)Browser DI scope
Interactive AutoServer package in server project; browser package in client projectEach runtime creates its own session
dotnet add package Toggly.FeatureManagement.Blazor --version 3.7.0
# Server project only:
dotnet add package Toggly.FeatureManagement.Blazor.Server --version 3.7.0

All Toggly .NET packages share version 3.7.0. The server package reuses trusted Toggly.FeatureManagement; the browser package reuses portable Toggly.FeatureManagement.Client and has no dependency on server evaluation or filesystem storage. The two Blazor packages target net8.0; use a compatible ASP.NET Core host and supported .NET runtime.

Trusted server setup​

using Toggly.FeatureManagement.Configuration;
using Toggly.FeatureManagement.Blazor.Server;

builder.Services.AddToggly(options =>
{
options.AppKey = builder.Configuration["TOGGLY_APP_KEY"] ?? "";
options.Environment = "Production";
options.UseSignedDefinitions = true;
options.UndefinedEnabledOnDevelopment = false;
});
builder.Services.AddTogglyBlazorServer();

Keep the backend App Key in trusted server configuration. AddTogglyBlazorServer registers native Toggly filters and a singleton targeting accessor whose values exist only inside one asynchronous evaluation flow. It never puts mutable circuit identity in a singleton. Existing server definition polling, WebSocket updates, usage reporting, snapshots and signature policy remain owned by the trusted SDK. RefreshAsync on this adapter re-evaluates the current cached definitions; it does not force a server network fetch.

Server snapshots across process restarts​

Server persistence is opt-in. Register an implementation of the trusted SDK's Toggly.FeatureManagement.IFeatureSnapshotProvider with server dependency injection to save definitions and public signing keys across process restarts. With UseSignedDefinitions = true, the SDK verifies the saved signature against the original signed definition bytes before applying a restored snapshot. The Blazor adapter does not register a filesystem provider for you.

The Sample's filesystem adapter is application code implementing that interface. In the Blazor Sample, set TOGGLY_SNAPSHOT_DIRECTORY to an absolute private directory outside the application content root and source checkout and supply the backend App Key through trusted server configuration. The adapter separates snapshots by app key and environment. Start online first to populate the directory, then stop the process. To exercise a cold offline restart, keep the same directory, app key and environment and set TOGGLY_NETWORK_MODE=offline. This Sample option denies SDK HTTP/gRPC and confines the SDK's direct WebSocket attempt to 127.0.0.1:1; ensure that port is unused. It retains the real snapshot namespace and does not replace definitions with fixtures. Leave the directory option unset to keep filesystem persistence disabled.

Use one application host per directory and protect it with service-account permissions, including appropriate Windows ACLs. Both the saved definitions and public JWKS are trusted local application state: replacing both can defeat verification against that stored key set. Trusted .NET 3.7.0 does not enforce an offline maximum signature age or expiry based on the persisted JWKS timestamp. Offline operation cannot learn about signing-key revocation or flag changes. Hosts that require an offline expiry policy must implement that policy explicitly; the browser's signature-age settings do not apply to this server provider.

These snapshots belong to the server process. Browser BrowserSnapshotStore and prerender hydration have separate storage and trust boundaries, described below. Persistent definitions do not make an Interactive Server application available without its server or provide offline delivery of WebAssembly assets.

Browser setup​

In the WebAssembly client project's Program.cs:

using Toggly.FeatureManagement.Blazor;
using Toggly.FeatureManagement.Client;

builder.Services.AddScoped(_ => new HttpClient());
builder.Services.AddTogglyBlazorWebAssembly(_ => new TogglyClientOptions
{
AppKey = builder.Configuration["Toggly:FrontendAppKey"],
Environment = "Production",
Defaults = new Dictionary<string, bool> { ["new-dashboard"] = false }
});

Obtain an additional Front-end App Key from App Settings and enable Available to Client SDK for each exposed flag. Configure exact allowed browser origins. No Blazor-specific technology picker is required. Everything in client configuration is public: never use a backend or management credential here.

The browser fetches evaluated-signed definitions, forwards identity/groups/claims to the evaluation endpoint, verifies signed responses using WebCrypto, and evaluates returned entity gates locally. BrowserSnapshotStore uses durable localStorage, partitioned by the portable client's context key (definitions endpoint, app, environment and normalized user context). After a verified response, the portable client persists the signed envelope and the exact accepted public key set. A new browser runtime can restore that snapshot before contacting the definitions service; it checks the cache format, context, configured key restrictions, signature age and signature again. Invalid, expired or corrupt snapshots fall back to defaults. Storage denial or quota limits leave network and in-memory evaluation available.

Persisted public keys are local application state originally learned from the configured HTTPS endpoint. This supports offline restart after a successful online initialization, but cannot defend against an attacker replacing both the local key set and its signed envelope. Configure out-of-band AllowedKeyIds or authoritative TrustedJwks when the host requires independent key trust. An explicit trusted key set is not silently replaced after verification failure. The default 30-day maximum signature age applies when restoring or accepting an envelope; already accepted in-memory definitions remain last-known-good during an outage.

Offline definitions do not install an offline application shell: WebAssembly assets and host configuration must already be available through the application's own offline delivery. Interactive Server and static SSR still require the server. MaximumSignatureAge, AllowedKeyIds, TrustedJwks, RefreshInterval, EnableLiveUpdates, LocalGates, BaseUri and WebSocketBaseUri are portable client options; HTTPS/WSS endpoints are required.

A missing browser key makes initialization use defaults without remote calls. Unknown keys default OFF. Network/signature failure preserves valid last-known definitions or defaults and raises Error. Polling and WebSocket-triggered refresh are owned and canceled by the portable client.

Components​

Add the namespaces to _Imports.razor:

@using Toggly.FeatureManagement.Blazor
@using Toggly.FeatureManagement.Client

Wrap a render-mode subtree in FeatureProvider. The provider initializes trusted sessions during component initialization and browser sessions after the first interactive render, when JavaScript is available.

<FeatureProvider>
<Feature Key="new-dashboard">
<ChildContent><p>New dashboard</p></ChildContent>
<Loading><p>Loading feature definitions…</p></Loading>
</Feature>
<Feature Key="new-dashboard" Negate="true">
<p>Classic dashboard</p>
</Feature>
<Feature Key="beta-access" Negate="true">
<p>Join the beta waitlist.</p>
</Feature>
<Feature Keys="@(new[] { "new-dashboard", "api-v2" })"
Requirement="Requirement.All">
<p>Both features are enabled.</p>
</Feature>
</FeatureProvider>

ChildContent is the content rendered when the combined evaluation is true. Use a second Feature with identical keys, requirement and entity plus Negate="true" for complementary content. Put Loading on one of the pair so pending content is not duplicated; Razor requires the ordinary content to use the named ChildContent fragment when Loading is named. Use Requirement.Any for at least one flag. An empty list evaluates true, then Negate applies. Do not combine Key and Keys: when Keys is supplied it takes precedence. Components subscribe to changes and marshal reevaluation through the renderer dispatcher; they unsubscribe on disposal.

Boolean content selection is not named variant/experiment assignment. The Blazor session API exposes boolean evaluation; use the trusted .NET variant API separately where appropriate. Presentation gates do not replace authorization of backend actions.

Programmatic evaluation and identity​

@inject IFeatureSession Features

@code {
private Task<bool> UseApiV2() => Features.EvaluateAsync(["api-v2"]);
private Task Refresh() => Features.RefreshAsync();
private Task ChangeDemoUser() => Features.SetContextAsync(new EvaluationContext(
Identity: "alice",
Groups: ["vip"],
Claims: new Dictionary<string, string> { ["role"] = "admin" }));
}

The session is scoped. Do not register it as a singleton or call SetContextAsync on a shared user client. A server evaluation snapshots context before awaiting, and restores the previous ambient context afterward. Browser context changes clear previous definitions before fetching the new user's result. Entity context belongs to each evaluation.

When AuthenticationStateProvider is registered, FeatureProvider maps the current authenticated user and follows authentication changes. The default selector uses NameIdentifier (falling back to Name), role claims as groups, and the first value for each claim type. Anonymous/logout maps to empty context. Supply ContextSelector for a different mapping. Browser claim values are untrusted targeting hints. The trusted adapter exposes BlazorTargetingContext.Current for custom filters, including scoped claims; it does not register HTTP-context-based segment or UserClaims filters for long-lived circuits.

Subscribe to Features.Changed and Features.Error for application status UI; dispatch UI work through InvokeAsync and unsubscribe in Dispose. The DI scope owns and disposes its session. Reconnecting an Interactive Server circuit reevaluates that circuit's existing context against current definitions; authentication changes still come from the host's authentication provider.

Order entity gate​

Configure context kind Order, key property Id, and boolean property Vip. Bind ExpressCheckout to Order and set its ContextProperty condition to Vip equals true.

<Feature Key="ExpressCheckout" Entity="order">
<button>Express checkout</button>
</Feature>
<Feature Key="ExpressCheckout" Entity="order" Negate="true">
<p>Standard checkout</p>
</Feature>

@code {
private readonly EntityContext order = new("Order", "ord-vip",
new Dictionary<string, object?> { ["Vip"] = true });
}

For a standard order use Vip=false. Browser entity gates fail closed without their required entity. Trusted server evaluation passes a canonical TogglyEvaluationContext into the .NET SDK. Arbitrary domain objects must first be mapped to the public EntityContext shape.

Prerender and hydration​

Use the same component subtree in the server and client projects for Auto. A FeatureHydration boundary can carry explicitly public, non-entity boolean results across renderers:

@using Microsoft.AspNetCore.Components.Web
<FeatureProvider>
<FeatureHydration PublicKeys="@(new[] { "new-dashboard", "api-v2" })"
RenderMode="@(new InteractiveAutoRenderMode())">
<Feature Key="new-dashboard"><p>New dashboard</p></Feature>
</FeatureHydration>
</FeatureProvider>

PublicKeys defaults to empty. The persistent payload contains only allowlisted key/boolean pairs, never backend credentials, definitions, user claims or entity attributes. Only allowlist flags that may be exposed to that browser user. Use a unique StateKey for multiple boundaries. Hydration is presentation state, not signed authorization evidence; browser initialization replaces it with independently verified definitions. A context/definition change invalidates it. Entity evaluations are never satisfied by the boolean hydration snapshot.

Static SSR has no interactive event handlers. Interactive Server retains a circuit, WebAssembly runs in the browser, and Auto can select a different runtime on a later visit; it does not transfer a live server DI scope into the browser.

Filter and telemetry boundaries​

CapabilityTrusted Blazor serverBrowser
AlwaysOn, Percentage, Targeting, TimeWindowExisting .NET native filtersEvaluation endpoint
ContextPropertyExisting .NET entity evaluatorLocal returned entity gate
UserClaims / country / UA / language / device / OSNot registered by this circuit adapter; custom filters require explicit scoped inputsEvaluation endpoint; browser headers reflect the actual browser, not demo overrides
Live definitionsTrusted provider polling and push notificationsPortable polling and WebSocket invalidation
Usage / custom metrics / named variantsUnderlying trusted SDK APIs, not new Blazor session methodsNo telemetry or variant-assignment API in the portable client

Runnable workshop​

The Blazor sample has actual SSR, Server, WebAssembly and Auto routes; declarative/programmatic gates; identity; Order entities; a filter matrix; refresh and failure exercises; and explicit missing-key behavior. Its source-reading map points to the registration, gate, evaluation, authentication and framework boundaries.