Chatter.MessageBrokers.AzureServiceBus
3.0.2
dotnet add package Chatter.MessageBrokers.AzureServiceBus --version 3.0.2
NuGet\Install-Package Chatter.MessageBrokers.AzureServiceBus -Version 3.0.2
<PackageReference Include="Chatter.MessageBrokers.AzureServiceBus" Version="3.0.2" />
<PackageVersion Include="Chatter.MessageBrokers.AzureServiceBus" Version="3.0.2" />
<PackageReference Include="Chatter.MessageBrokers.AzureServiceBus" />
paket add Chatter.MessageBrokers.AzureServiceBus --version 3.0.2
#r "nuget: Chatter.MessageBrokers.AzureServiceBus, 3.0.2"
#:package Chatter.MessageBrokers.AzureServiceBus@3.0.2
#addin nuget:?package=Chatter.MessageBrokers.AzureServiceBus&version=3.0.2
#tool nuget:?package=Chatter.MessageBrokers.AzureServiceBus&version=3.0.2
Chatter.MessageBrokers.AzureServiceBus
Azure Service Bus transport for Chatter.MessageBrokers: queue receivers, topic subscriptions, sessions and cross-entity transactions.
This package connects the Chatter.MessageBrokers abstractions to Azure Service Bus. Commands arrive on queues, Events arrive on topic subscriptions, and your handlers send and publish through the same IMessageHandlerContext they use for in-process dispatch. It uses one shared ServiceBusClient per namespace and creates no entities: queues, topics, subscriptions and session-enabled entities must already exist. Part of the Chatter suite.
Contents
- Features
- Installation
- Quick start
- Receiving
- Sending and publishing
- Sessions
- Message lock renewal
- Transactions
- Authentication
- Configuration
- Retry and circuit breaker
- Diagnostics
- Related packages
- Learn more
- License
Features
- Queue receivers and topic subscriptions: bind Commands to queues and Events to topic subscriptions with one registration call each, or through the
[BrokeredMessage]attribute. - Per-receiver concurrency: set
MaxConcurrentCallsglobally or per receiver; a value stated on the receiver wins. - Sessions: FIFO delivery per session, several sessions held at once, the session id surfaced as the Group Id, and durable session state.
- Message lock renewal: PeekLock message locks are renewed while a handler runs, up to a configurable ceiling.
- Cross-entity transactions: settle the received message and the messages your handler sends in one Azure Service Bus transaction.
- SAS or token authentication: use a connection string with a shared access key, or any
TokenCredentialagainst an endpoint-only connection string. - Configuration or fluent setup: bind options from any configuration source, such as
appsettings.json, user secrets or environment variables; an explicit fluent call wins over configuration. - Transient fault detection: Azure Service Bus transient errors feed the Chatter.MessageBrokers retry and circuit breaker recovery policies.
Installation
dotnet add package Chatter.MessageBrokers.AzureServiceBus
Targets .NET 10 (net10.0).
Dependencies: Chatter.MessageBrokers, Azure.Messaging.ServiceBus 7.20.2.
Companion packages:
- Chatter.MessageBrokers.AzureServiceBus.Auth for Microsoft Entra ID token authentication.
- Chatter.MessageBrokers.Reliability.EntityFramework or Chatter.MessageBrokers.Reliability.Cosmos for a durable Inbox and Outbox.
Quick start
The samples use WebApplication.CreateBuilder(args) (builder.Services, builder.Configuration) with implicit usings enabled. Any IServiceCollection with an IConfiguration works the same way.
1. Define your messages
using Chatter.CQRS.Commands;
using Chatter.CQRS.Events;
public class PlaceOrder : ICommand
{
public Guid OrderId { get; set; }
}
public class OrderPlaced : IEvent
{
public Guid OrderId { get; set; }
}
2. Handle them
using Chatter.CQRS;
using Chatter.CQRS.Context;
public class PlaceOrderHandler : IMessageHandler<PlaceOrder>
{
public async Task Handle(PlaceOrder message, IMessageHandlerContext context)
{
// place the order, then announce it on the "order-events" topic
await context.Publish(new OrderPlaced { OrderId = message.OrderId }, "order-events");
}
}
public class BillOrderHandler : IMessageHandler<OrderPlaced>
{
public Task Handle(OrderPlaced message, IMessageHandlerContext context)
{
// bill the order
return Task.CompletedTask;
}
}
3. Register the transport and its receivers
builder.Services.AddChatterCqrs(builder.Configuration, typeof(Program).Assembly)
.AddMessageBrokers()
.AddAzureServiceBus(asb => asb
.WithConnectionString(builder.Configuration.GetConnectionString("ServiceBus"))
.WithMaxConcurrentCalls(4)
.AddQueueReceiver<PlaceOrder>("orders", errorQueuePath: "orders-errors")
.AddTopicSubscription<OrderPlaced>("order-events", "billing"));
Note: A message type registered with
AddQueueReceiver,AddTopicSubscriptionor their session variants must not also carry the[BrokeredMessage]attribute. Registration throwsInvalidOperationExceptionwhen it does.
4. Add the connection string
Store the namespace connection string as a user secret, not in appsettings.json, because it contains a shared access key:
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:ServiceBus" "Endpoint=sb://<namespace>.servicebus.windows.net/;SharedAccessKeyName=<key-name>;SharedAccessKey=<key>"
For production, and to connect without a key, see Storing secrets.
The orders and orders-errors queues, the order-events topic and its billing subscription must exist in the namespace.
5. Send a command from your API
using Chatter.MessageBrokers.Sending;
app.MapPost("/orders", async (PlaceOrder command, IBrokeredMessageDispatcher dispatcher) =>
{
await dispatcher.Send(command, "orders");
return Results.Accepted();
});
The command is sent to the orders queue, received by the queue receiver and handled by PlaceOrderHandler.
Receiving
Registration methods
Register receivers inside the AddAzureServiceBus(asb => ...) delegate. Each method has two overloads: one inherits the global MaxConcurrentCalls, and one takes a required int maxConcurrentCalls directly after the path parameters.
| Method | Description |
|---|---|
AddQueueReceiver<TMessage>(queueName, ...) |
Receives a Command (TMessage : class, ICommand) from a queue. |
AddTopicSubscription<TMessage>(topicName, subscriptionName, ...) |
Receives an Event (TMessage : class, IEvent) from a topic subscription. |
AddSessionQueueReceiver<TMessage>(queueName, ...) |
Receives a Command from a session-enabled queue. See Sessions. |
AddSessionTopicSubscription<TMessage>(topicName, subscriptionName, ...) |
Receives an Event from a session-enabled topic subscription. See Sessions. |
Receiver parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
maxConcurrentCalls |
int |
global MaxConcurrentCalls |
Messages processed at once; in session mode, sessions held at once. Only on the second overload. A value below 1 throws ArgumentOutOfRangeException at registration. |
errorQueuePath |
string |
null |
Queue a message is routed to once its receive attempts are exhausted. |
description |
string |
null |
Free-text description of the receiver. |
transactionMode |
TransactionMode? |
global TransactionMode |
Overrides the Chatter.MessageBrokers transaction mode for this receiver. See Transactions. |
maxReceiveAttempts |
int |
10 |
Delivery attempts before the message is dead-lettered and routed to errorQueuePath. |
using Chatter.MessageBrokers.Receiving;
builder.Services.AddChatterCqrs(builder.Configuration, typeof(Program).Assembly)
.AddMessageBrokers()
.AddAzureServiceBus(asb => asb
.WithConnectionString(builder.Configuration.GetConnectionString("ServiceBus"))
.AddQueueReceiver<PlaceOrder>(
"orders",
maxConcurrentCalls: 8,
errorQueuePath: "orders-errors",
transactionMode: TransactionMode.ReceiveOnly,
maxReceiveAttempts: 5)
.AddTopicSubscription<OrderPlaced>("order-events", "shipping", maxConcurrentCalls: 2));
Note: Passing the literal
defaultin themaxConcurrentCallsposition is ambiguous between the two overloads and does not compile. Pass a typed value or omit the argument.
Receivers declared with an attribute
Receivers found by the Chatter.MessageBrokers [BrokeredMessage] assembly scan also run on Azure Service Bus when it is the default infrastructure. A sending path that is empty or equal to the receiving path means a queue; a different sending path means a topic named by the sending path and a subscription named by the receiving path. Attribute receivers always use the global MaxConcurrentCalls.
using Chatter.CQRS.Events;
using Chatter.MessageBrokers;
// topic "order-events", subscription "billing"
[BrokeredMessage("order-events", "billing", errorQueueName: "billing-errors")]
public class OrderPlaced : IEvent
{
public Guid OrderId { get; set; }
}
Receive mode
The transaction mode decides how messages are received. TransactionMode.None receives in ReceiveAndDelete mode, so Azure Service Bus removes the message on receipt and a failed handler loses it. ReceiveOnly (the Chatter.MessageBrokers default) and FullAtomicityViaInfrastructure receive in PeekLock mode, so the message is completed, abandoned or dead-lettered after the handler runs.
Sending and publishing
From a handler
Handlers send Commands and publish Events through the Send and Publish extensions on IMessageHandlerContext (namespace Chatter.CQRS.Context). The outbound message inherits the entire inbound message context; see Inbound context inheritance.
using Chatter.CQRS;
using Chatter.CQRS.Context;
public class OrderPlacedHandler : IMessageHandler<OrderPlaced>
{
public Task Handle(OrderPlaced message, IMessageHandlerContext context)
=> context.Send(new ShipOrder { OrderId = message.OrderId }, "shipping");
}
From outside a handler
Inject IBrokeredMessageDispatcher (scoped) and call Send or Publish with a destination path, as in the Quick start.
Choosing the infrastructure
When your application registers more than one broker, context.AzureServiceBus() marks the inbound message for Azure Service Bus and returns the IMessageBrokerContext, whose Send, Publish and Forward go out over Azure Service Bus. It returns null when the handler was not invoked by a Brokered Message Receiver. Outside a handler, select the infrastructure on the options with options.UseMessagingInfrastructure(t => t.AzureServiceBus()).
await context.AzureServiceBus().Publish(new OrderPlaced { OrderId = message.OrderId }, "order-events");
Azure Service Bus message properties
Set these ASBMessageContext keys on SendOptions or PublishOptions with WithMessageContext (namespace Chatter.MessageBrokers.Routing.Options). WithMessageContext returns RoutingOptions, so call it as its own statement.
| Key | Value type | Maps to |
|---|---|---|
ASBMessageContext.ScheduledEnqueueTimeUtc |
DateTime (UTC) |
ServiceBusMessage.ScheduledEnqueueTime. A value of any other type is ignored. |
ASBMessageContext.PartitionKey |
string |
ServiceBusMessage.PartitionKey. With a Group Id set, it is applied only when non-empty and must equal the Group Id. |
ASBMessageContext.ViaPartitionKey |
string |
ServiceBusMessage.TransactionPartitionKey. |
ASBMessageContext.To |
string |
ServiceBusMessage.To. |
using Chatter.MessageBrokers.AzureServiceBus;
using Chatter.MessageBrokers.Routing.Options;
var options = new SendOptions();
options.WithMessageContext(ASBMessageContext.ScheduledEnqueueTimeUtc, DateTime.UtcNow.AddMinutes(10));
await dispatcher.Send(new PlaceOrder { OrderId = orderId }, "orders", options: options);
Sessions
Azure Service Bus sessions deliver messages that share a SessionId to one receiver in strict FIFO order. Chatter maps the session id to the Group Id: inbound, it appears under MessageContext.GroupId; outbound, SendOptions.WithGroupId sets ServiceBusMessage.SessionId. Session-enabled queues and subscriptions (RequiresSession = true) must already exist; this package does not create or enable them.
Registering a session receiver
builder.Services.AddChatterCqrs(builder.Configuration, typeof(Program).Assembly)
.AddMessageBrokers()
.AddAzureServiceBus(asb => asb
.WithConnectionString(builder.Configuration.GetConnectionString("ServiceBus"))
.AddSessionQueueReceiver<ProcessOrder>("orders-session", maxConcurrentCalls: 4)
.AddSessionTopicSubscription<OrderPlaced>("order-events", "billing-session"));
Sessions held at once
A session receiver holds up to MaxConcurrentCalls sessions at once, and each held session still delivers its messages one at a time in FIFO order. At the default of 1, a receiver holds one session.
- Handlers must be thread-safe. Above
1, handlers for different sessions run concurrently in one process. - Sizing multiplies across replicas. With M replicas and
MaxConcurrentCallsN, up to M×N sessions are locked at once. Size N against the number of sessions on the entity.
Reading the session id
using Chatter.CQRS;
using Chatter.CQRS.Context;
using Chatter.MessageBrokers;
public class ProcessOrderHandler : IMessageHandler<ProcessOrder>
{
public Task Handle(ProcessOrder message, IMessageHandlerContext context)
{
string? sessionId = null;
if (context.GetInboundBrokeredMessage()?.MessageContext.TryGetValue(MessageContext.GroupId, out var groupId) == true)
{
sessionId = groupId as string;
}
// correlate work within this session
return Task.CompletedTask;
}
}
Sending to a session
using Chatter.MessageBrokers.Routing.Options;
await context.Send(new ProcessOrder { OrderId = id }, "orders-session", new SendOptions().WithGroupId(id.ToString()));
WithGroupId is enough on its own. A partition key is optional; if you set one, it must equal the Group Id, and a mismatch throws ArgumentOutOfRangeException from the Azure SDK before the message is sent:
using Chatter.MessageBrokers.AzureServiceBus;
using Chatter.MessageBrokers.Routing.Options;
var options = new SendOptions().WithGroupId(id.ToString());
options.WithMessageContext(ASBMessageContext.PartitionKey, id.ToString());
Inbound context inheritance
A handler that sends or publishes through IMessageHandlerContext inherits the entire inbound message context: every application property on the inbound message, plus the values the receiver sets from the delivery. That includes the CorrelationId, Subject, ReplyTo, ReplyToSessionId, To, TimeToLive and Group Id, so a message received in a session is sent with the same SessionId. On a plain queue or topic an inherited Group Id has no effect; on a session-enabled or partitioned destination it does. Chatter does not authenticate any of these values; see Inbound header trust.
To opt out, supply your own Group Id on the outbound options, because options you supply win the merge. Alternatively, send through the IBrokeredMessageDispatcher overload that takes a TransactionContext instead of an IMessageHandlerContext; that overload does not merge the inbound context.
Session state
During handling, read, write and clear durable state stored on the entity for the held session. These extensions on IMessageHandlerContext throw InvalidOperationException for a message that did not arrive through a session receiver.
using Chatter.CQRS;
using Chatter.CQRS.Context;
public class ProcessOrderHandler : IMessageHandler<ProcessOrder>
{
public async Task Handle(ProcessOrder message, IMessageHandlerContext context)
{
BinaryData? state = await context.GetSessionStateAsync(); // null when no state is set
await context.SetSessionStateAsync(BinaryData.FromString($"last-processed:{message.OrderId}"));
// when the session's work is complete:
// await context.ClearSessionStateAsync();
}
}
Session timing
| Option | Builder method | Default | Description |
|---|---|---|---|
SessionIdleTimeout |
WithSessionIdleTimeout(TimeSpan) |
00:01:00 |
How long a held session may yield no message before it is released and the receiver moves to the next session. |
MaxSessionLockRenewalDuration |
WithMaxSessionLockRenewalDuration(TimeSpan) |
00:05:00 |
Ceiling on renewing a held session's lock during long processing. After it, renewal stops and the session lock expires. |
Both apply to session receivers only.
Message lock renewal
For non-session receivers in PeekLock mode, the package renews each delivery's message lock while its handler runs, so a handler that outlasts the entity's lock duration keeps its lock. Renewal is tracked per delivery and stops at the first of: the delivery's settlement, the delivery's release after handling, the receiver closing, or the MaxMessageLockRenewalDuration ceiling (default 5 minutes). A handler that runs past the ceiling loses its lock and the message is redelivered.
Set the ceiling with WithMaxMessageLockRenewalDuration(TimeSpan) or the MaxMessageLockRenewalDuration key. Zero or a negative duration turns message lock renewal off. ReceiveAndDelete deliveries have no lock to renew.
Transactions
The transaction mode comes from the receiver's transactionMode argument, or from the global Chatter.MessageBrokers TransactionMode (see Chatter.MessageBrokers configuration).
A receiver's own transactionMode wins over the global mode. Set the global mode on AddMessageBrokers; a Chatter:MessageBrokers:TransactionMode key in configuration wins over this fluent call:
using Chatter.MessageBrokers.Receiving;
builder.Services.AddChatterCqrs(builder.Configuration, typeof(Program).Assembly)
.AddMessageBrokers(options => options.WithTransactionMode(TransactionMode.FullAtomicityViaInfrastructure))
.AddAzureServiceBus(asb => asb
.WithConnectionString(builder.Configuration.GetConnectionString("ServiceBus"))
.AddQueueReceiver<PlaceOrder>("orders"));
| Mode | Receive mode | Behaviour |
|---|---|---|
None |
ReceiveAndDelete |
No transaction. The message is removed on receipt. |
ReceiveOnly |
PeekLock |
The message is settled after the handler runs. Sends from the handler are not part of the receive transaction. |
FullAtomicityViaInfrastructure |
PeekLock |
Settlement and the handler's sends commit or roll back together in one Azure Service Bus cross-entity transaction. |
Cross-entity transactions are enabled on the shared client when any receiver's effective mode is FullAtomicityViaInfrastructure, or when you call WithCrossEntityTransactions() (or set EnableCrossEntityTransactions to true). With them on, Azure Service Bus pins the client to one top-level entity, so the host may register receivers for only one queue or one topic. Startup throws InvalidOperationException listing the entities when more than one distinct top-level receiver entity is registered; subscriptions on the same topic count once.
Important: The local Azure Service Bus emulator does not support transactions that span entities. Test
FullAtomicityViaInfrastructureagainst a real namespace.
Authentication
Shared access signature
A connection string that contains SharedAccessKeyName and SharedAccessKey, or a SharedAccessSignature, authenticates with SAS. This is the default.
Token credential
Pass any Azure.Core.TokenCredential to AddTokenProvider with an endpoint-only connection string, such as Endpoint=sb://<namespace>.servicebus.windows.net/, which holds no secret and can live in appsettings.json. The credential is used only when the connection string contains no SAS; with SAS present, SAS is used. The Func<TokenCredential> overload is called immediately, at registration.
using Azure.Identity; // from the Azure.Identity package
builder.Services.AddChatterCqrs(builder.Configuration, typeof(Program).Assembly)
.AddMessageBrokers()
.AddAzureServiceBus(asb => asb
.WithConnectionString(builder.Configuration.GetConnectionString("ServiceBus"))
.AddTokenProvider(new DefaultAzureCredential())
.AddQueueReceiver<PlaceOrder>("orders"));
Microsoft Entra ID with the Auth package
Chatter.MessageBrokers.AzureServiceBus.Auth adds builder extensions for client secret, certificate, interactive and managed identity authentication, for example asb.UseAadTokenProviderWithManagedIdentity(). See its README.
Storing secrets
A connection string that contains SharedAccessKey or SharedAccessSignature is a secret. Keep it out of appsettings.json and source control: in development, store it with .NET user secrets as in the Quick start; in production, supply it from an environment variable or Azure Key Vault. As environment variables, ConnectionStrings:ServiceBus is ConnectionStrings__ServiceBus and the configuration-section key is Chatter__Infrastructure__AzureServiceBus__ConnectionString.
To store no key at all, use an endpoint-only connection string with a token credential, such as managed identity through the Auth package; see Token credential and Microsoft Entra ID with the Auth package.
Configuration
Configuration section
AddAzureServiceBus binds ServiceBusOptions from Chatter:Infrastructure:AzureServiceBus whenever that section exists. Choose another section with UseConfig("<section>"). Keys you omit keep their defaults. Supply the ConnectionString key from a secret store, not this file; see Storing secrets.
{
"Chatter": {
"Infrastructure": {
"AzureServiceBus": {
"MaxConcurrentCalls": 4,
"PrefetchCount": 0,
"EnableCrossEntityTransactions": false,
"SessionIdleTimeout": "00:01:00",
"MaxSessionLockRenewalDuration": "00:05:00",
"MaxMessageLockRenewalDuration": "00:05:00",
"RetryPolicy": {
"NoRetry": false,
"MaximumRetryCount": 5,
"MinimumBackoffInSeconds": 1,
"MaximumBackoffInSeconds": 30,
"DeltaBackoffInSeconds": 0
}
}
}
}
}
With the connection string in configuration, registration needs no fluent calls:
builder.Services.AddChatterCqrs(builder.Configuration, typeof(Program).Assembly)
.AddMessageBrokers()
.AddAzureServiceBus(asb => asb.AddQueueReceiver<PlaceOrder>("orders"));
Options
| Option | Type | Default | Description |
|---|---|---|---|
ConnectionString |
string |
— (required) | Namespace connection string. Registration throws when neither configuration nor WithConnectionString supplies one. |
MaxConcurrentCalls |
int |
1 |
Messages processed at once per receiver; sessions held at once for a session receiver. Must be at least 1, checked when the receiver starts. |
PrefetchCount |
int |
0 |
Messages fetched ahead of the handler. Passed to the Azure SDK receiver options. |
EnableCrossEntityTransactions |
bool |
false |
Forces cross-entity transactions on. See Transactions. |
SessionIdleTimeout |
TimeSpan |
00:01:00 |
See Session timing. |
MaxSessionLockRenewalDuration |
TimeSpan |
00:05:00 |
See Session timing. |
MaxMessageLockRenewalDuration |
TimeSpan |
00:05:00 |
See Message lock renewal. |
RetryPolicy:NoRetry |
bool |
false |
true turns Azure SDK client retry off. See Client retry policy. |
RetryPolicy:MaximumRetryCount |
int? |
SDK default | Maps to ServiceBusRetryOptions.MaxRetries. |
RetryPolicy:MinimumBackoffInSeconds |
double? |
SDK default | Maps to ServiceBusRetryOptions.Delay. |
RetryPolicy:MaximumBackoffInSeconds |
double? |
SDK default | Maps to ServiceBusRetryOptions.MaxDelay. |
RetryPolicy:DeltaBackoffInSeconds |
double? |
— | Accepted and ignored. |
RetryOptions |
ServiceBusRetryOptions |
null |
Resolved client retry options. Set through the builder or RetryPolicy; not bindable directly. |
TokenCredential |
TokenCredential |
null |
Set with AddTokenProvider; not reachable from configuration. |
Builder methods
| Method | Description |
|---|---|
WithConnectionString(string) |
Sets the connection string. |
WithMaxConcurrentCalls(int) |
Sets the global MaxConcurrentCalls. |
WithPrefetchCount(int) |
Sets PrefetchCount. |
WithCrossEntityTransactions(bool enabled = true) |
Sets EnableCrossEntityTransactions. |
WithSessionIdleTimeout(TimeSpan) |
Sets SessionIdleTimeout. |
WithMaxSessionLockRenewalDuration(TimeSpan) |
Sets MaxSessionLockRenewalDuration. |
WithMaxMessageLockRenewalDuration(TimeSpan) |
Sets MaxMessageLockRenewalDuration. |
WithNoRetry() |
Turns client retry off (MaxRetries = 0). |
WithExponentialDelay(int maximumRetryCount, double maximumBackoffInSeconds, double minimumBackoffInSeconds, double deltaBackoffInSeconds) |
Exponential client retry with MaxRetries, MaxDelay and Delay from the arguments. deltaBackoffInSeconds is ignored. |
AddTokenProvider(TokenCredential) |
Supplies a token credential. See Authentication. |
AddTokenProvider(Func<TokenCredential>) |
Same, from a factory called immediately. |
UseConfig(string configSectionName = "Chatter:Infrastructure:AzureServiceBus") |
Binds options from the named section. |
AddServiceBusOptions(string configSectionName) |
Obsolete; use UseConfig. |
Precedence
An explicit fluent call wins over configuration, in either direction: WithMaxConcurrentCalls(1) overrides a configured 5. A key present in configuration wins over the default, and an option set by neither keeps the default. WithNoRetry() and WithExponentialDelay(...) override the whole RetryPolicy section, which is then never read.
Chatter.MessageBrokers resolves the other way: its configuration wins over its fluent calls. An application that configures both packages sees the same-looking call behave differently in each.
Per-receiver concurrency
A maxConcurrentCalls stated on a receiver registration wins over the global value, whatever source the global value came from. The global value is resolved first (fluent, then configuration, then default), and a stated per-receiver value then replaces it for that receiver. A per-receiver value cannot be set in configuration, and attribute receivers always use the global value. Registering the same receiver path twice with different stated values throws.
Every injection style resolves the same options instance
ServiceBusOptions is registered as the concrete type and as IOptions<ServiceBusOptions>, IOptionsSnapshot<ServiceBusOptions> and IOptionsMonitor<ServiceBusOptions>. All four resolve the one fully built instance, and no half-built instance can be resolved. The options are built once and never reload, so IOptionsMonitor change callbacks never fire. A services.Configure<ServiceBusOptions>(...) registration is not consulted; configure the options fluently or through the configuration section.
Prefetch tuning
PrefetchCount is not a throughput setting for a slow handler. A prefetched message's lock starts ageing when it is fetched, not when a handler picks it up, so with a slow handler the Nth buffered message waits roughly N handler durations. In session mode it ages against the session lock. Raise MaxConcurrentCalls for throughput instead.
Other APIs
| Method | Description |
|---|---|
context.AzureServiceBus() |
Routes the handler's outbound messages over Azure Service Bus and returns IMessageBrokerContext; null outside a receiver-invoked handler. |
InfrastructureTypes.AzureServiceBus() |
The Azure Service Bus infrastructure type, for UseMessagingInfrastructure(t => t.AzureServiceBus()). |
pipeline.WithTransactionScopeSupressionBehavior() |
Command Pipeline behavior (namespace Chatter.MessageBrokers.AzureServiceBus.Receiving) that suppresses the ambient TransactionScope around command handling when the message was received with a TransactionContext. |
Retry and circuit breaker
Receive-side recovery
Failed receives and handlers are retried by the Chatter.MessageBrokers retry and circuit breaker policies (see Recovery). This package tells those policies which errors are transient: a ServiceBusException whose IsTransient is true, or whose Reason is ServiceCommunicationProblem, ServiceBusy or ServiceTimeout. After maxReceiveAttempts deliveries, a message is dead-lettered and, by default, routed to the receiver's errorQueuePath.
Client retry policy
The Azure SDK ServiceBusClient also retries transient failures on the wire. Its ServiceBusRetryOptions are resolved once. A fluent WithNoRetry() or WithExponentialDelay(...) wins outright; otherwise:
| Configuration | Resulting client retry options |
|---|---|
No Chatter:Infrastructure:AzureServiceBus section |
Unset (null); the Azure SDK uses its defaults. |
Section present, no RetryPolicy subsection |
ServiceBusRetryOptions with the SDK defaults. |
RetryPolicy:NoRetry is true |
MaxRetries = 0; client retry is off. |
Any other RetryPolicy |
Exponential mode. Each stated key maps to its SDK setting; each omitted key keeps the SDK default. |
A stated value is passed to the SDK unchanged, so MaximumRetryCount: 0 also turns retry off; NoRetry says so explicitly. DeltaBackoffInSeconds is accepted so existing sections still bind, and is ignored because the Azure SDK has no equivalent.
Where each key is validated
- A key of the wrong type fails at registration with an
InvalidOperationExceptionnaming the full key path. RetryPolicyvalues are validated by the Azure SDK'sServiceBusRetryOptionssetters at registration; an out-of-range value stops the host, and the error does not name the configuration key.MaxConcurrentCallsbelow1fails when the receiver starts, naming the receiver and the value.PrefetchCountis validated by the Azure SDK when a receiver is created.DeltaBackoffInSecondsis never validated, because it is ignored.
Diagnostics
This package emits no telemetry of its own. Broker spans and metrics come from the Chatter.MessageBrokers ActivitySource and Meter; see Chatter.MessageBrokers diagnostics. With tracing on, the W3C traceparent header rides the message application properties in both directions (see Trace context propagation).
Diagnostic-Id interop
The Azure Service Bus SDK stamps its legacy Diagnostic-Id only when a message carries no correlation identifier. Because Chatter writes traceparent, the SDK is expected not to stamp Diagnostic-Id on messages Chatter sends. The presence of both mechanisms in the shipped SDK assemblies is verified; the short-circuit itself comes from the Azure SDK's published source and is not verified in this repository.
If your correlation depends on Diagnostic-Id, enable the SDK's ActivitySource support so it reads traceparent. Set it at process start, before the first Azure SDK type is used:
AZURE_EXPERIMENTAL_ENABLE_ACTIVITY_SOURCE=true
AppContext.SetSwitch("Azure.Experimental.EnableActivitySource", true);
Semantic convention spelling
Chatter's broker spans follow OpenTelemetry semantic conventions v1.30.0 and emit messaging.operation.type. Azure.Messaging.ServiceBus emits the older messaging.operation. A mixed trace shows both spellings; Chatter emits one spelling per concept.
Related packages
- Chatter.CQRS: In-process Commands, Queries, Events and the Command Pipeline.
- Chatter.MessageBrokers: The broker abstractions this transport implements: receivers, routing, Inbox/Outbox and Recovery.
- Chatter.MessageBrokers.AzureServiceBus.Auth: Microsoft Entra ID token authentication for this transport.
- Chatter.MessageBrokers.Reliability.EntityFramework: EF Core Inbox, Outbox and Unit of Work.
- Chatter.MessageBrokers.Reliability.Cosmos: Azure Cosmos DB Inbox and Outbox Relay.
Learn more
License
Licensed under the MIT License.
| 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.Messaging.ServiceBus (>= 7.20.2)
- Chatter.MessageBrokers (>= 0.35.3)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Chatter.MessageBrokers.AzureServiceBus:
| Package | Downloads |
|---|---|
|
Chatter.MessageBrokers.AzureServiceBus.Auth
Provides Azure AD authentication for Chatter.MessageBrokers.AzureServiceBus, building Azure.Identity credentials (client secret, client certificate, interactive browser, and managed identity) for Azure Service Bus. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.2 | 465 | 9/30/2026 |
| 3.0.1 | 407 | 9/24/2026 |
| 3.0.0 | 98 | 9/24/2026 |
| 2.5.3 | 106 | 9/23/2026 |
| 2.5.2 | 121 | 9/18/2026 |
| 2.5.1 | 98 | 9/18/2026 |
| 2.5.0 | 101 | 9/18/2026 |
| 2.4.1 | 106 | 9/17/2026 |
| 2.4.0 | 249 | 9/11/2026 |
| 2.3.0 | 112 | 9/9/2026 |
| 2.2.0 | 115 | 9/4/2026 |
| 2.1.1 | 139 | 9/2/2026 |
| 2.1.0 | 128 | 9/1/2026 |
| 2.0.1 | 128 | 9/1/2026 |
| 2.0.0 | 285 | 8/31/2026 |
| 1.4.3 | 116 | 8/30/2026 |
| 1.4.2 | 128 | 8/30/2026 |
| 1.4.1 | 308 | 8/22/2026 |
| 1.4.0 | 1,623 | 6/16/2026 |
| 0.8.0 | 318 | 5/30/2026 |