Cloudstrap.Messaging
0.2.0-preview.83
Prefix Reserved
dotnet add package Cloudstrap.Messaging --version 0.2.0-preview.83
NuGet\Install-Package Cloudstrap.Messaging -Version 0.2.0-preview.83
<PackageReference Include="Cloudstrap.Messaging" Version="0.2.0-preview.83" />
<PackageVersion Include="Cloudstrap.Messaging" Version="0.2.0-preview.83" />
<PackageReference Include="Cloudstrap.Messaging" />
paket add Cloudstrap.Messaging --version 0.2.0-preview.83
#r "nuget: Cloudstrap.Messaging, 0.2.0-preview.83"
#:package Cloudstrap.Messaging@0.2.0-preview.83
#addin nuget:?package=Cloudstrap.Messaging&version=0.2.0-preview.83&prerelease
#tool nuget:?package=Cloudstrap.Messaging&version=0.2.0-preview.83&prerelease
Cloudstrap.Messaging
A durable Wolverine messaging node in one call: local, Azure Service Bus or SQL Server transports selected by configuration, suffix conventions and workload routing for dependency-free message contracts, a transactional EF Core inbox/outbox, bounded retries and dead-lettering, business correlation on the suite's shared vocabulary, and OpenTelemetry that rides whatever pipeline the host already has. No hand-assembled bus, no lost or duplicated messages.
Wolverine's own types stay first-class in your code: inject IMessageBus to send, publish or invoke,
write plain Wolverine handlers, and use IDbContextOutbox<TDbContext> on the HTTP path. There is no
Cloudstrap facade over the bus.
Quick start
// Producer (an HTTP host, e.g. the demo Api)
builder.AddCloudstrapMessaging() // Cloudstrap:Messaging section
.UseSqlServer() // durable inbox/outbox on ConnectionStrings:DefaultConnection
.AddCloudstrapTransactionalMessaging<OrdersDbContext>(); // entity + message commit atomically
// Consumer (a headless node, e.g. the demo Worker)
builder.AddCloudstrapMessaging()
.UseSqlServer()
.AddCloudstrapTransactionalMessaging<WorkerDbContext>();
With no Cloudstrap:Messaging section at all, AddCloudstrapMessaging() runs an in-process node:
no network, no SQL, no Azure — the whole suite works on a fresh clone. Configuration alone flips it
to a broker.
Handlers are plain Wolverine handlers, discovered in the host's entry assembly:
public static class PlaceOrderCommandHandler
{
public static async Task Handle(PlaceOrderCommand command, WorkerDbContext db, IMessageBus bus)
{
db.Orders.Add(new Order { Id = command.OrderId });
await bus.PublishAsync(new OrderPlacedEvent(command.OrderId)); // committed with the row
}
}
Non-handler code (an endpoint, a background job) gets the same atomicity through the outbox — the explicit three-line pattern:
IDbContextOutbox<OrdersDbContext> outbox = /* injected */;
outbox.DbContext.Orders.Add(order); // 1. stage the entity
await outbox.SendAsync(new PlaceOrderCommand(order.Id)); // 2. stage the message
await outbox.SaveChangesAndFlushMessagesAsync(); // 3. one transaction, then dispatch
Settings — Cloudstrap:Messaging
Every convention has an override. Connection strings are resolved by name through the standard
ConnectionStrings: section; no setting in this section ever carries a secret.
| Key | Default | Meaning |
|---|---|---|
Transport |
Local |
Local, AzureServiceBus or SqlServer. An unknown value fails at the call, naming the key. |
EndpointName |
Cloudstrap:Application workload name ({system}-{subsystem}-{type}) |
The node's identity: its inbox queue and the subscriptions it creates. |
AutoProvision |
null → on in Development only |
Create queues, topics, subscriptions and durability tables at startup. An explicit value wins. |
AzureServiceBus:FullyQualifiedNamespace |
— | contoso.servicebus.windows.net, authenticated with DefaultAzureCredential. Required on ASB unless the connection-string name resolves. |
AzureServiceBus:ConnectionStringName |
— | Name of a ConnectionStrings: entry — the local-emulator fallback. |
SqlTransport:ConnectionStringName |
DefaultConnection |
The database holding the queue tables (SQL Server transport). |
SqlTransport:SchemaName |
Wolverine's default | Schema of the queue tables. Sender and listener must share it. |
Durability:ConnectionStringName |
DefaultConnection |
The database holding the message store (UseSqlServer()). |
Durability:SchemaName |
sanitized workload name | Schema of the inbox/outbox/dead-letter tables; contoso-orders-worker → contoso_orders_worker. |
Retries:NumberOfImmediate |
5 |
In-process retries before the scheduled stage. |
Retries:NumberOfDelayed |
5 |
Scheduled retries with a doubling cooldown (5 s, 10 s, 20 s, …) before dead-lettering. |
DeadLetter:QueueName |
{SystemName}-error |
The transport-level error queue, where one materializes. |
Destinations |
empty | Command routing map: key = message namespace or type-name prefix, value = destination workload (endpoint) name. |
Destinations is a dictionary: the configuration binder adds to it. Entries set in code and in
configuration merge, a key present in both takes the configuration value, and configuration cannot
remove an entry added in code.
The sibling block consumed here (owned by Cloudstrap.Core, shared with the HTTP correlation
middleware of Cloudstrap.Observability):
| Key | Default | Meaning |
|---|---|---|
Cloudstrap:Correlation:HeaderName |
X-Correlation-ID |
The envelope header carrying the business correlation id — the same header HTTP uses. |
Cloudstrap:Correlation:Message:RequireForAllMessageHandlers |
false |
Every handler requires a correlation id; sends without one are blocked too. |
Cloudstrap:Correlation:Message:ExcludeMessageHandlers |
empty | Full type names of handlers exempt from the requirement. |
Conventions and routing (workload-centric topology)
Message contracts need zero package references: classification is by type-name suffix.
| Suffix | Kind | Azure Service Bus | SQL Server |
|---|---|---|---|
*Command, *Message |
command-like | sent to the destination workload's queue via Destinations |
queue via Destinations |
*Event |
event | published to a topic per event type; each consuming workload subscribes under its own endpoint name | queue via Destinations (queues only) |
| anything else | unclassified | handled locally, or routed explicitly | same |
- The node listens on its own queue, named after its endpoint (workload) name.
- A type this node handles locally is handled locally, ahead of any convention route. The
Destinationsmap is for the commands a node sends elsewhere. - Wolverine sanitizes broker identifiers: Azure Service Bus names are lowercased; SQL Server queue
and schema identifiers replace
-with_. - Every rule is a replaceable delegate on
MessageConventions(Classify,DestinationFor,TopicNameFor), adjusted through the configurator;configurator.Wolverineruns last with full control of the engine:
builder.AddCloudstrapMessaging(configurator =>
{
configurator.Conventions = conventions =>
conventions.DestinationFor = type => type.Namespace!.StartsWith("Contoso.Billing") ? "contoso-billing-worker" : null;
configurator.Wolverine = options =>
options.PublishMessage<AuditRecordedEvent>().ToAzureServiceBusTopic("audit"); // explicit routes always win
});
One startup log line states the posture in force: transport, endpoint name, every destination,
durability, dead-letter posture and the effective AutoProvision value.
Durability and dead-lettering
- Without a provider the node runs buffered and non-durable, and says so at startup.
UseSqlServer()turns on the durable inbox/outbox and durable local queues. The store lives in a schema per workload, so many workloads share one database without collision (the isolation unit is a schema, not a table-name prefix). With the SQL Server transport the store lives on the transport's database.- Failed messages exhaust the retry ladder and land in the store's
wolverine_dead_letterstable — queryable and replayable. The{SystemName}-errorname applies to the transport-level error queue wherever one materializes (a non-durable Azure Service Bus node, for example). - The retry ladder is the engine's last global failure rule: exception-specific rules added
through
configurator.Wolverine(options.Policies.OnException<T>()) match first. - Logging on failure carries the message type and id, never the payload.
AddCloudstrapTransactionalMessaging<TDbContext>() requires a durability provider; without
UseSqlServer() the host fails at startup naming it. The two calls compose in any order. Note that
EF Core's EnsureCreated is a no-op once Wolverine's tables exist in a database: create your own
tables through migrations, or before the node starts.
Correlation
The business correlation id flows on the configured header from the ambient
ICorrelationContextAccessor (set by the HTTP middleware, or by you) onto every outgoing envelope,
and back into the accessor on the receiving side — so a remote handler sees the original inbound
value. W3C traceparent flows through OpenTelemetry regardless.
Enforcement uses the suite's one vocabulary: RequireForAllMessageHandlers, [CorrelationRequired]
on a handler method, class or base class, [AllowNoCorrelation] to exempt one, and
ExcludeMessageHandlers to exempt by name. A blocked message raises a CorrelationRequiredException
naming the header and the handler and is dead-lettered without retries; a send without a
correlation id while every handler requires one is blocked the same way, at the call.
Observability
Wolverine's ActivitySource and Meter (both named Wolverine) are registered additively into
whatever OpenTelemetry pipeline the host builds — Cloudstrap's owner or contribute mode, a
consumer's own, or Aspire ServiceDefaults. This package registers no exporter and no provider; with
no pipeline at all it is inert and the host still starts.
Security baseline
- Credentials never live in this section: Azure Service Bus uses
DefaultAzureCredential(environment, workload identity, managed identity); the connection-string fallback is resolved by name. - Validation failures, startup logs and exceptions name configuration keys, never values.
- Transport encryption is TLS in transit and Azure Service Bus encryption at rest. Property-level message encryption is deliberately not provided.
- One node per process: a second
AddCloudstrapMessaging()call throws at the call site.
Verifying against a real Azure Service Bus namespace (manual procedure)
Never automated — the test suite touches no network. To prove the transport end to end:
- Create a namespace and grant your identity Azure Service Bus Data Owner (provisioning needs management rights; runtime needs Data Sender/Receiver).
- Configure two hosts (the demo Api and Worker work) with
Transport = AzureServiceBus,AzureServiceBus:FullyQualifiedNamespace = <name>.servicebus.windows.net,AutoProvision = truefor the first run, aDestinationsentry from the contracts namespace to the consumer's workload name, andCloudstrap:OpenTelemetrypointed at Application Insights. - Start the consumer, then the producer; send a command with an
X-Correlation-IDheader on the producer's HTTP endpoint. - Verify in the portal: the consumer's queue (
{workload}), a topic per published event type with a subscription named after the consumer, and the{SystemName}-errorqueue. - Verify in Application Insights: one operation spanning producer and consumer (
traceparent), and the sameX-Correlation-IDon both sides' logs.
Migration notes (deliberate changes from the source library)
- Engine swap, no wire compatibility. Wolverine envelopes are not NServiceBus-compatible; a Cloudstrap node cannot exchange messages with an existing NServiceBus endpoint.
- Durability isolation is a schema, not a table prefix — same shared-database guarantee.
- Dead-lettering moved from an error queue to the durable store's dead-letter table; the
{SystemName}-errornaming convention is kept where a transport queue materializes. - No XML fallback deserializer — System.Text.Json only, by default.
- No audit queue, no ServicePlatform heartbeats or metrics — OpenTelemetry replaces them.
- Installer gating by an environment string became the explicit
AutoProvisionoption with aDevelopmentdefault. - Credential selection by host sniffing became
DefaultAzureCredentialwith no secret-bearing configuration keys. - The command-executor mediator is not ported —
IMessageBus.InvokeAsyncplus transactional middleware is the supported path; functional result types are your own choice. - A second registration fails fast instead of being silently tolerated.
| 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
- Azure.Identity (>= 1.21.0)
- Cloudstrap.Core (>= 0.2.0-preview.83)
- Cloudstrap.Observability (>= 0.2.0-preview.83)
- Microsoft.EntityFrameworkCore (>= 10.0.10)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.10)
- Microsoft.Extensions.Configuration (>= 10.0.10)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Hosting (>= 10.0.10)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
- 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)
- WolverineFx (>= 6.31.0)
- WolverineFx.AzureServiceBus (>= 6.31.0)
- WolverineFx.EntityFrameworkCore (>= 6.31.0)
- WolverineFx.RuntimeCompilation (>= 6.31.0)
- WolverineFx.SqlServer (>= 6.31.0)
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 |
|---|---|---|
| 0.2.0-preview.83 | 34 | 9/3/2026 |