Cloudstrap.Extensions
0.2.0-preview.83
Prefix Reserved
dotnet add package Cloudstrap.Extensions --version 0.2.0-preview.83
NuGet\Install-Package Cloudstrap.Extensions -Version 0.2.0-preview.83
<PackageReference Include="Cloudstrap.Extensions" Version="0.2.0-preview.83" />
<PackageVersion Include="Cloudstrap.Extensions" Version="0.2.0-preview.83" />
<PackageReference Include="Cloudstrap.Extensions" />
paket add Cloudstrap.Extensions --version 0.2.0-preview.83
#r "nuget: Cloudstrap.Extensions, 0.2.0-preview.83"
#:package Cloudstrap.Extensions@0.2.0-preview.83
#addin nuget:?package=Cloudstrap.Extensions&version=0.2.0-preview.83&prerelease
#tool nuget:?package=Cloudstrap.Extensions&version=0.2.0-preview.83&prerelease
Cloudstrap.Extensions
KeyVault-backed configuration, Azure Blob data protection, a conventional blob container client,
config-driven typed HttpClients with correlation and access-token seams, and the standard health probe
endpoints — one call and one Cloudstrap: subsection each.
Runtime requirement: this package carries a
Microsoft.AspNetCore.Appframework reference. Every consumer requires the ASP.NET Core shared framework at run time —mcr.microsoft.com/dotnet/aspnetbase images work;mcr.microsoft.com/dotnet/runtime-only base images are not supported. Blazor WebAssembly clients are served byCloudstrap.BlazorWasm, never by this package.
Quick start
var builder = WebApplication.CreateBuilder(args);
builder.AddCloudstrapKeyVault(); // first: secrets take part in everything bound below
builder.UseCloudstrapObservability();
builder.AddCloudstrapBlobStorage();
builder.AddCloudstrapDataProtection();
builder.Services.AddCloudstrapHttpServiceClient<ICatalogClient, CatalogClient>("Catalog");
var app = builder.Build();
app.MapCloudstrapHealthChecks(); // /healthz and /ready
app.MapControllers();
app.Run();
{
"Cloudstrap": {
"Application": { "SystemName": "Contoso", "SubsystemName": "Orders", "SubsystemType": "Api" },
"KeyVault": { "Enabled": true, "VaultUri": "https://contoso-vault.vault.azure.net/" },
"Storage": { "BlobServiceUri": "https://contosostore.blob.core.windows.net/" },
"DataProtection": {
"Enabled": true,
"KeysBlobUri": "https://contosostore.blob.core.windows.net/keys/keys.xml",
"KeyVaultKeyId": "https://contoso-vault.vault.azure.net/keys/dataprotection"
},
"HttpClients": {
"Catalog": { "BaseAddress": "https://catalog.contoso.example/", "EnableHealthCheck": true }
}
}
}
Every AddCloudstrap… call above is safe to leave in unconditionally. Configuration decides where each one
does something, so the same Program.cs runs on a laptop and in production.
KeyVault-backed configuration
AddCloudstrapKeyVault() adds the vault as a configuration source when Cloudstrap:KeyVault:Enabled is
set. Secrets are filtered by prefix and their flat names are mapped onto nested configuration keys:
| Secret in the vault | Configuration key |
|---|---|
contoso-orders-api-ConnectionStrings--Orders |
ConnectionStrings:Orders |
contoso-orders-api-Catalog--ApiKey |
Catalog:ApiKey |
other-workload-Secret |
(not loaded) |
The prefix filter is the reason this exists. Several workloads can share one vault without reading each
other's secrets. The prefix defaults to Cloudstrap:Application:WorkloadName — the lowercase
{SystemName}-{SubsystemName}-{SubsystemType} — and is overridable:
| Setting | Default | Notes |
|---|---|---|
Cloudstrap:KeyVault:Enabled |
false |
Nothing Azure-related is even constructed when this is off. |
Cloudstrap:KeyVault:VaultUri |
— | Required, and absolute, when enabled. |
Cloudstrap:KeyVault:SecretPrefix |
the workload name | An explicit prefix wins; "" loads every secret unfiltered. |
Call it first, before GetCloudstrapOptions() and the other registrations, so secrets participate in
options binding. The source is added last, so vault values win over appsettings.json — standard
configuration layering.
Failures are deliberately loud: enabling the section without a VaultUri throws
ConfigurationValidationException before the host exists, and an unreachable vault fails the configuration
build. An application must never start believing its secrets are simply absent.
The credential and reload interval are code-level, not configuration:
builder.AddCloudstrapKeyVault(settings =>
{
settings.Credential = new ManagedIdentityCredential("<client-id>");
settings.ReloadInterval = TimeSpan.FromHours(1); // default: read once at startup
});
Use Cloudstrap's KeyVault configuration or Aspire's — never both
Both add a KeyVault configuration source. Two sources over one vault means two sets of providers, doubled startup calls, and last-one-wins precedence that depends on registration order. Pick one owner:
- Cloudstrap's, when you want the secret-prefix filter — one vault serving several workloads. This is the capability Aspire's integration does not provide.
- Aspire's, when the vault serves exactly one application and you want it wired through the AppHost.
Then leave
Cloudstrap:KeyVault:Enabledatfalse; everything else in this package works unchanged.
Cloudstrap references no Aspire.* package and never will.
Required permissions
| Feature | Role on the resource |
|---|---|
| KeyVault configuration | Key Vault Secrets User on the vault |
| Blob storage | Storage Blob Data Contributor on the account or container |
| Data protection | Storage Blob Data Contributor on the key container, plus a KeyVault key policy granting wrap and unwrap |
Credentials default to DefaultAzureCredential everywhere — the same code path resolves a managed identity
in Azure and your own sign-in locally. There is no environment sniffing and no credential-type exclusion
list; supply a TokenCredential through the configure hook when you need something specific.
Typed HTTP clients
builder.Services.AddCloudstrapHttpServiceClient<ICatalogClient, CatalogClient>("Catalog");
The client name defaults to the interface name without its leading I (ICatalogClient binds
Cloudstrap:HttpClients:CatalogClient); pass name to override it.
| Setting | Default | Notes |
|---|---|---|
BaseAddress |
— | Required, absolute. |
Timeout |
00:00:30 |
|
AddUserAccessToken / AddClientAccessToken |
false |
See the token seam below. |
EnableHealthCheck |
false |
Registers a readiness check probing the peer. |
HealthCheckPrefix |
the client name | Names the check {prefix}-liveness. |
HealthCheckPath |
/healthz |
Probed on the peer, relative to BaseAddress. |
A missing section, or a BaseAddress that is absent or relative, fails at host startup — or at first
resolution of the client, whichever comes first — with a message naming the offending key. A misconfigured
client never reaches the network.
The correlation handler is attached exactly once, even when you already registered it through
ConfigureHttpClientDefaults. Your configureClient and configureBuilder hooks run after Cloudstrap's own
wiring, so they always have the final say.
Resilience is yours
Cloudstrap never adds a resilience handler and never replaces the primary handler. Retries, circuit breakers and hedging stay your choice, applied however you prefer:
builder.Services.ConfigureHttpClientDefaults(http => http.AddStandardResilienceHandler());
Cloudstrap-registered clients pick that up like any other client, with exactly one resilience layer — the one
you asked for. This is also what makes the package composable with an Aspire ServiceDefaults project that
applies resilience at the defaults level.
Access tokens: the seam
Setting AddUserAccessToken or AddClientAccessToken activates the corresponding token handler seam. This
package declares the two interfaces and never implements them, so nothing here depends on an authentication
stack. The implementations ship separately:
| Flag | Seam interface | Implementing package |
|---|---|---|
AddUserAccessToken |
IUserAccessTokenHandlerProvider |
Cloudstrap.Authentication.OpenIdConnect |
AddClientAccessToken |
IClientAccessTokenHandlerProvider |
Cloudstrap.Authentication.ClientCredentials |
Each provider is resolved from the container when the client's pipeline is first built, never at registration
time, so the order of your AddCloudstrap… calls does not matter. A client may set both flags: both handlers
are then added, user first. Until an implementation is registered, setting a flag fails client creation with
a message naming exactly the missing flag(s) and package(s) — Cloudstrap will not quietly send an
unauthenticated, or partially authenticated, request in its place.
Health probes
app.MapCloudstrapHealthChecks() serves both standard probes, filtered by the shared tag vocabulary from
Cloudstrap.Observability:
| Path (configurable) | Tag | Meaning |
|---|---|---|
/healthz — Cloudstrap:HealthChecks:LivenessPath |
live |
The process is functioning. A failure here means restart me. |
/ready — Cloudstrap:HealthChecks:ReadinessPath |
ready |
The instance can serve traffic. A failure here means take me out of rotation. |
Both are anonymous and short-circuited, and use the framework's own response writer. Setting
Cloudstrap:HealthChecks:Enabled to false maps nothing; calling the method twice maps one set.
Checks register additively on the stock IHealthChecksBuilder, so AddHealthChecks().AddCheck(...) — yours
or another library's — composes with Cloudstrap's without special handling. A client with
EnableHealthCheck adds a readiness-tagged URI check named {HealthCheckPrefix ?? name}-liveness that
probes BaseAddress + HealthCheckPath and judges by status code. An unreachable dependency therefore takes
the instance out of rotation without provoking a restart loop.
The probe has its own named HttpClient, {name}-liveness, which you can reconfigure like any other client
— that is the seam for a custom handler, proxy or timeout.
Blob storage
AddCloudstrapBlobStorage() registers a single BlobContainerClient for the container the application works
against.
| Setting | Default | Notes |
|---|---|---|
Cloudstrap:Storage:BlobServiceUri |
— | Required unless a connection string is supplied. |
Cloudstrap:Storage:ContainerName |
SystemName lowercased |
|
Cloudstrap:Storage:ConnectionString |
— | Wins when set. UseDevelopmentStorage=true targets Azurite. |
Cloudstrap:Storage:CreateContainerIfNotExists |
false |
When no connection string is configured in the section, the standard
ConnectionStrings:CloudstrapStorage entry is honored, so platform tooling can supply one the usual way.
Targeting a local emulator is an explicit setting, never an inferred environment. Container creation stays opt-in because creating storage usually belongs to deployment and needs rights the application should not hold; when you do ask for it, a failure surfaces rather than being swallowed.
Data protection
AddCloudstrapDataProtection() persists the key ring to blob storage and encrypts it with a KeyVault key, so
cookies and antiforgery tokens issued by one instance are readable by every other and survive a restart.
| Setting | Default | Notes |
|---|---|---|
Cloudstrap:DataProtection:Enabled |
false |
Disabled leaves the framework's own key storage in place. |
Cloudstrap:DataProtection:KeysBlobUri |
— | Required when enabled. |
Cloudstrap:DataProtection:KeyVaultKeyId |
— | Required when enabled. |
Cloudstrap:DataProtection:ApplicationName |
the workload name | Isolates payloads; set two apps to the same value to share deliberately. |
Encryption is not optional by design. Keys written unencrypted because a setting was forgotten is exactly the failure this prevents, so a missing key identifier fails startup naming it. If you genuinely want blob persistence without envelope encryption, configure the framework's own chain instead:
builder.Services.AddDataProtection().PersistKeysToAzureBlobStorage(uri, credential);
Name collision:
Cloudstrap.Extensions.DataProtectionOptionsshares its simple name withMicrosoft.AspNetCore.DataProtection.DataProtectionOptions. Qualify or alias whichever you mean when both namespaces are in scope.
Escape hatches
Every convention here has an override, and where a framework primitive is a better fit, use it directly:
| Instead of | Use |
|---|---|
| A custom probe response body | the framework's MapHealthChecks with your own writer |
| Data protection without KeyVault encryption | the framework's AddDataProtection() chain |
| A dependency check that is not a Cloudstrap peer | AddHealthChecks().AddUrlGroup(...) |
| Resilience | ConfigureHttpClientDefaults or per-client AddStandardResilienceHandler |
Verifying KeyVault against a real vault
KeyVault is the one feature no automated test can prove — a test would have to contact a live vault. Run this once against a real vault to confirm the behavior end to end.
Create a vault and note its URI, for example
https://contoso-vault.vault.azure.net/.Grant yourself access. Assign
Key Vault Secrets Userto the identity you will run as (your own sign-in for a local run, the app's managed identity in Azure).Add two secrets, where
{prefix}is your workload name — the lowercase{SystemName}-{SubsystemName}-{SubsystemType}, e.g.contoso-orders-api:{prefix}-Demo--Messagewith valuehello from key vaultother-Ignoredwith valuemust not be loaded
Point the application at the vault:
{ "Cloudstrap": { "KeyVault": { "Enabled": true, "VaultUri": "https://contoso-vault.vault.azure.net/" } } }Run the application and read configuration key
Demo:Message— for example injectIConfigurationand logconfiguration["Demo:Message"].Expect:
Demo:Messageresolves tohello from key vault— the prefix was stripped and--became:. Readingother-Ignoredorother:Ignoredyields nothing: the filter kept another workload's secret out.Verify precedence. Add
"Demo": { "Message": "from appsettings" }toappsettings.jsonand run again. Expect: the vault value still wins, because the vault source is added last.Verify the fail-fast. Change
VaultUrito a vault that does not exist and run again. Expect: startup fails with an Azure request error rather than starting with the secret missing. Then removeVaultUrientirely while leavingEnabledattrue. Expect: aConfigurationValidationExceptionnamingCloudstrap:KeyVault:VaultUri.Verify the off switch. Set
Enabledtofalse. Expect: the application starts, contacts no vault, andDemo:Messagefalls back to whateverappsettings.jsonprovides.
Dependencies
All OSI-licensed. AspNetCore.HealthChecks.Uris (Xabaril) is Apache-2.0; every other dependency is MIT.
Azure.Extensions.AspNetCore.Configuration.Secrets · Azure.Extensions.AspNetCore.DataProtection.Blobs ·
Azure.Extensions.AspNetCore.DataProtection.Keys · Azure.Identity · Azure.Storage.Blobs ·
AspNetCore.HealthChecks.Uris · Cloudstrap.Core · Cloudstrap.Observability
There is no authentication package here, and no Aspire.* package anywhere in the closure.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- AspNetCore.HealthChecks.Uris (>= 9.0.0)
- Azure.Extensions.AspNetCore.Configuration.Secrets (>= 1.5.1)
- Azure.Extensions.AspNetCore.DataProtection.Blobs (>= 1.5.3)
- Azure.Extensions.AspNetCore.DataProtection.Keys (>= 1.6.3)
- Azure.Identity (>= 1.21.0)
- Azure.Storage.Blobs (>= 12.29.1)
- Cloudstrap.Core (>= 0.2.0-preview.83)
- Cloudstrap.Observability (>= 0.2.0-preview.83)
- OpenTelemetry.Exporter.Console (>= 1.17.0)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.17.0)
- OpenTelemetry.Extensions.Hosting (>= 1.17.0)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.17.0)
- OpenTelemetry.Instrumentation.Http (>= 1.17.0)
- OpenTelemetry.Instrumentation.Runtime (>= 1.17.0)
- OpenTelemetry.Instrumentation.SqlClient (>= 1.17.0)
- Serilog (>= 4.4.0)
- Serilog.Extensions.Hosting (>= 10.0.0)
- Serilog.Sinks.Console (>= 6.1.1)
- Serilog.Sinks.File (>= 7.0.0)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on Cloudstrap.Extensions:
| Package | Downloads |
|---|---|
|
Cloudstrap.Worker
Worker-service bootstrap for the .NET generic host: validated Cloudstrap configuration (fail-fast at the call), correlation, additive health-check registration, and a minimal Kestrel health listener serving /healthz + /ready from the host's registered checks on a configurable port. One call and one Cloudstrap:Worker section. |
|
|
Cloudstrap.Authentication.ClientCredentials
Machine-to-machine OAuth 2.0 client-credentials tokens on Duende AccessTokenManagement: one call, and every flagged Cloudstrap typed HttpClient transparently carries a cached, renewed bearer token — isolated token cache by default, secret-free client-assertion support, and failure that is loud, lazy and never leaks a credential. |
|
|
Cloudstrap.BlazorServer
Blazor Server bootstrap for ASP.NET Core: one registration call and one fixed-order pipeline call — hardened antiforgery and security headers, HSTS, correlation, anonymous health probes, conditional auth placement, Interactive Server or static SSR decided once, and an interaction trace scope that makes circuit-originated work visible; pairs with Cloudstrap's OIDC login and typed HttpClients. |
|
|
Cloudstrap.Authentication.OpenIdConnect
Interactive OpenID Connect login on the stock ASP.NET Core handlers and Duende AccessTokenManagement: one call for authorization-code + PKCE sign-in against any standards-compliant identity provider, a hardened __Host-Cloudstrap session cookie, transparent user-token refresh, and the signed-in user's token on every flagged Cloudstrap typed HttpClient. |
|
|
Cloudstrap.WebApi
Web API bootstrap for ASP.NET Core: API versioning, one OpenAPI document per version with a Scalar reference UI, RFC 9457 problem-details error handling, correlation, health probes, security headers, HSTS and CORS, plus optional hardened JWT bearer validation — two calls and one Cloudstrap: subsection each. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.2.0-preview.83 | 45 | 9/3/2026 |
| 0.2.0-preview.2 | 96 | 8/27/2026 |