Getting Started
Architecture
NServiceBus
Transports
Persistence
Hosting
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Modernization
Samples

Configuration

Configuring an endpoint

To use Azure Service Bus as the underlying transport:

var transport = new AzureServiceBusTransport("Endpoint=sb://[NAMESPACE].servicebus.windows.net/;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]", TopicTopology.Default);
endpointConfiguration.UseTransport(transport);

Connectivity

These settings control how the transport connects to the broker.

Transport

  • UseWebSockets: Configures the transport to use AMQP over websockets.
transport.UseWebSockets = true;
  • WebProxy: Configures an optional web-proxy to use with AMQP over websockets.
transport.WebProxy = new System.Net.WebProxy("http://myproxy:8080");
  • TimeToWaitBeforeTriggeringCircuitBreaker: The time to wait before triggering the circuit breaker after a critical error occurred. Defaults to 2 minutes.
transport.TimeToWaitBeforeTriggeringCircuitBreaker = TimeSpan.FromMinutes(2);

Retry-policy

  • RetryPolicyOptions: Allows replacement of the default retry options.
var azureAsbRetryOptions = new Azure.Messaging.ServiceBus.ServiceBusRetryOptions
{
    Mode = Azure.Messaging.ServiceBus.ServiceBusRetryMode.Exponential,
    MaxRetries = 5,
    Delay = TimeSpan.FromSeconds(0.8),
    MaxDelay = TimeSpan.FromSeconds(15)
};
transport.RetryPolicyOptions = azureAsbRetryOptions;

Token-credentials

Enables usage of Microsoft Entra ID authentication such as managed identities for Azure resources instead of the shared secret in the connection string.

var transportWithTokenCredentials = new AzureServiceBusTransport("[NAMESPACE].servicebus.windows.net", new DefaultAzureCredential(), TopicTopology.Default);
endpointConfiguration.UseTransport(transportWithTokenCredentials);

Entity creation

These settings control how the transport creates entities in the Azure Service Bus namespace.

Access rights

By default, the transport requires elevated privileges to manage namespace entities at runtime. If using a shared access policy, make sure to include Manage rights or the Azure Service Bus Data Owner role if authenticating using Managed Identities.

To avoid running with elevated privileges:

Topology

  • Topology: The topology used to publish and subscribe to events between endpoints. The topology is shared by the endpoints that need to publish and subscribe to events from each other. The topology has to be explicitly passed into the constructor.

Endpoints that do not require backward compatibility with the previous single-topic topology should be using TopicTopology.Default which represents the new default topic-per-event topology. For transports requiring compatibility during the migration towards the topic-per-event topology the upgrade guide describes the migration topology in more depth.

Topic names must adhere to the limits outlined in the Microsoft documentation on topic creation.

Mapping

Options

It is possible to configure a topology entirely from configuration by loading a serialized version of the options and using the TopicTopology.FromOptions to create the topology.

This allows loading the topology configuration from Application configuration or any other source. The options layer also provides support for source-generated serializer options as part of TopologyOptionsSerializationContext.

The following snippet demonstrates raw deserialization of options and creating the topology from those options. Usage may vary depending on the usage cases. For more details how to load options in the generic host consolidate the options sample.

using var stream = File.OpenRead("topology-options.json");
var options = JsonSerializer.Deserialize<TopologyOptions>(stream, TopologyOptionsSerializationContext.Default.Options);
var jsonTopology = TopicTopology.FromOptions(options);

The topology json document for the topic-per-event topology looks like:

{
  "$type": "topology-options",
  "PublishedEventToTopicsMap": {
    "MyNamespace.SomeEvent": "some-event"
  },
  "SubscribedEventToTopicsMap": {
    "MyNamespace.SomeEvent": "some-event"
  },
  "QueueNameToSubscriptionNameMap": {
    "Publisher": "PublisherSubscriptionName"
  },
  "ThrowIfUnmappedEventTypes": false
}

To enforce that each event type published has an explicit topic name mapping in PublishedEventToTopicsMap, set ThrowIfUnmappedEventTypes to true. This property defaults to false.

To support polymorphic event types, one event (base type) can be mapped to multiple topics (where the derived events are published):

{
  "$type": "topology-options",
  "PublishedEventToTopicsMap": {
    "MyNamespace.SomeEvent": "some-event"
  },
  "SubscribedEventToTopicsMap": {
    "MyNamespace.SomeEvent": [
      "some-event",
      "some-other-event"
    ]
  },
  "QueueNameToSubscriptionNameMap": {
    "Publisher": "PublisherSubscriptionName"
  }
}

Loading from json is also supported for the migration topology:

{
  "$type": "migration-topology-options",
  "TopicToPublishTo": "TopicToPublishTo",
  "TopicToSubscribeOn": "TopicToSubscribeOn",
  "EventsToMigrateMap": [
    "MyNamespace.NotYetMigratedEvent"
  ],
  "SubscribedEventToRuleNameMap": {
    "MyNamespace.NotYetMigratedEvent": "EventRuleName"
  },
  "PublishedEventToTopicsMap": {
    "MyNamespace.MigratedEvent": "MigratedEvent"
  },
  "SubscribedEventToTopicsMap": {
    "MyNamespace.MigratedEvent": "MigratedEvent"
  },
  "QueueNameToSubscriptionNameMap": {
    "Publisher": "PublisherSubscriptionName"
  }
}
Validation

During the start of the transport the topology configuration is validated against some of the well known limitations like entity, subscription or rule name lengths and some consistency validation is executed.

The default validator uses data validations and source-generated options validation. The default validator can be overriden or the validation can be entirely disabled.

transport.Topology.OptionsValidator = new TopologyOptionsDisableValidationValidator();

Disabling the validator might be desirable in generic hosting scenarios when the topology options are loaded from the Application configuration and the validator is registered to validate at startup to avoid double validating.

Hierarchy namespace

Azure Service Bus allows entities to be organized in hierarchies on the same namespace. In a multi-tenant environment, this can be used to group tenant related queues and topics. It can also be used to separate work done by different developers (in non-production environments), separate production and lower environments, or to create a temporary infrastructure for troubleshooting a bug.

From version 6.1 onward, the Azure Service Bus transport supports configuring hierarchical entities by setting the HierarchyNamespaceOptions property with a valid HierarchyNamespace:

transport.HierarchyNamespaceOptions = new HierarchyNamespaceOptions { HierarchyNamespace = "my-hierarchy" };

Doing so will prefix all entities with the {HierarchyNamespace}/{original-entity-path} format.

Escaping the hierarchy

In scenarios when a message must be sent outside of the hierarchy, designated message types or interfaces can defined and excluded from the hierarchy using the HierarchyNamespaceOptions.ExcludeMessageType<TMessageType>() method.

Considering the following message types for exclusion:

public class MyExcludedMessage {}

public interface IAmExcludedFromTheHierarchy {}

public class MyExcludedMessageByInterface : IAmExcludedFromTheHierarchy { }

public class MyOtherExcludedMessageByInterface : IAmExcludedFromTheHierarchy {}

The HierarchyNamespaceOptions can be configured to exclude them individually or by interface so that only these message types will be sent outside of the my-hierarchy hierarchy:

// exclude only a concrete type
transport.HierarchyNamespaceOptions.ExcludeMessageType<MyExcludedMessage>();
// exclude all types that inherit an interface or base type
transport.HierarchyNamespaceOptions.ExcludeMessageType<IAmExcludedFromTheHierarchy>();

Usage with topology mapping

The hierarchy namespace prefix is applied to all messages, so topology mappings do not need to specify the hierarchy namespace to utilize this feature. This also means that topology mappings are unable to "escape" the hierarchy if it is set.

If a topology mapping attempts to set the same hierarchy prefix that was specified through the HierarchyNamespaceOptions property, it will be ignored, but if the mapping attempts to set a different prefix, it will still be prepended with the hierarchy namespace.

For example, if a topology mapping specifies a destination of topology-ns/destination, the destination using the above options would become:

my-hierarchy/topology-ns/destination

For scenarios where this blanket hierarchy approach is not desired, there are two options:

  • Do not set the HierarchyNamespaceOptions property and configure topology mappings for destinations where a hierarchy is desired.

    This is better when most messages will not be within the hierarchy.

  • Set the HierarchyNamespaceOptions property and configure message types that should be excluded using the ExcludeMessageType<TMessageType>() method.

    This is better when most messages need to be in the hierarchy.

Settings

  • EntityMaximumSize: The maximum entity size in GB. The value must correspond to a valid value for the namespace type. Defaults to 5. See the Microsoft documentation on quotas and limits for valid values.
  • EnablePartitioning: Partitioned entities offer higher availability, reliability, and throughput over conventional non-partitioned queues and topics. For more information about partitioned entities see the Microsoft documentation on partitioned messaging entities.
  • AutoDeleteOnIdle: A TimeSpan representing the AutoDeleteOnIdle setting for instance-specific input queues (such as when using MakeInstanceUniquelyAddressable) created by the transport. This value is the maximum time period that a queue can remain idle before Azure Service Bus automatically deletes it. Defaults to TimeSpan.MaxValue in Azure Service Bus if this setting is not specified within the transport. The minimum allowed value is 5 minutes. The transport will not apply this setting to topics or subscriptions as these are considered shared infrastructure (along with shared queues such as error and audit).
  • AutoForwardDeadLetteredMessagesToErrorQueue: When enabled, dead-lettered messages from transport-created receive queues are auto-forwarded to the configured NServiceBus error queue. It's recommended to enable this option to centralize failed-message handling for dead-letter queues. For general Azure Service Bus forwarding behavior, see Enable auto forwarding for Azure Service Bus queues and subscriptions. This setting is nullable; when not explicitly configured, a warning is logged at runtime if a message is dead-lettered.
  • HierarchyNamespaceOptions: Beginning version 6.1, hierarchical entities can be configured using a hierarchical namespace. Setting this with the required HierarchyNamespace property will prefix all entity paths in the format {HierarchyNamespace}/{original-entity-path}. Defaults to HierarchyNamespaceOptions.None, which disables prefixing with a hierarchy namespace.
  • MaxDeliveryCount: The maximum delivery count applied to queues created during endpoint infrastructure setup. Defaults to int.MaxValue, which effectively disables Azure Service Bus's built-in delivery count limits and defers all retry decisions to NServiceBus recoverability. The Azure Service Bus Emulator requires a value of 10. When choosing a value, ensure it is high enough to allow the configured recoverability policy to eventually move the message to the error queue, but not so high that it creates effectively infinite retries.
  • ThrowOnMissingTopicWhenPublishing: When enabled, the transport re-throws the underlying ServiceBusException when publishing to a non-existent topic. The transport always logs a warning when publishing to a non-existent topic. Defaults to false for backward compatibility.

Controlling the prefetch count

When consuming messages from the broker, throughput can be improved by having the consumer prefetch additional messages. The prefetch count is calculated by multiplying maximum concurrency by the prefetch multiplier. The default value of the multiplier is 10, but it can be changed by using the following:

transport.PrefetchMultiplier = 3;

Alternatively, the whole calculation can be overridden by setting the prefetch count directly using the following:

transport.PrefetchCount = 100;

To disable prefetching, prefetch count should be set to zero.

Lock-renewal

For all supported transport transaction modes (except TransportTransactionMode.None), the transport utilizes a peek-lock mechanism to ensure that only one instance of an endpoint can process a message. The default lock duration is set during entity creation. By default, the transport uses the SDK's default maximum auto lock renewal duration of 5 minutes.

To ensure smooth processing, it is recommended to configuring the MaxAutoLockRenewalDuration property to be greater than the longest running handler for the endpoint. This helps avoid LockLostException and ensures the message is properly handled by the recoverability process.

The lock can be auto-renewed beyond the default by overriding the MaxAutoLockRenewalDuration.

transport.MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(10);

Dead lettering

Azure Service Bus provides a native dead-letter queue (DLQ) for each queue and subscription. NServiceBus can integrate with this mechanism, allowing failed messages to be dead-lettered natively and forwarded to the central NServiceBus error queue.

For background information on Azure Service Bus dead-letter queues, see Overview of Service Bus dead-letter queues.

Forward dead-lettered messages to the error queue

When queues are created by the transport, native dead-lettered messages can be auto-forwarded to the configured error queue:

transport.AutoForwardDeadLetteredMessagesToErrorQueue = true;

This setting is opt-in and affects only queues created by the transport. See the asb-transport provisioning commands for more details on scripting options.

Route all failed messages to the native DLQ

To route failed messages to the native Azure Service Bus dead-letter queue instead of the NServiceBus error queue, enable:

endpointConfiguration.Recoverability()
    .MoveErrorsToAzureServiceBusDeadLetterQueue();

Request dead lettering from recoverability

Use a custom recoverability policy to explicitly request dead lettering for selected failures.

Dead-letter with standard NServiceBus fault metadata:

endpointConfiguration.Recoverability()
    .CustomPolicy((config, errorContext) =>
    {
        if (errorContext.Exception is PoisonMessageException)
        {
            return RecoverabilityAction.DeadLetter();
        }

        return DefaultRecoverabilityPolicy.Invoke(config, errorContext);
    });

Dead-letter with custom reason, description, and modified application properties:

endpointConfiguration.Recoverability()
    .CustomPolicy((config, errorContext) =>
    {
        if (errorContext.Exception is MyBusinessException ex)
        {
            return RecoverabilityAction.DeadLetter(
                deadLetterReason: "Business rule validation failed",
                deadLetterErrorDescription: ex.Message,
                propertiesToModify: new Dictionary<string, object>
                {
                    ["FailureCategory"] = "Validation"
                });
        }

        return DefaultRecoverabilityPolicy.Invoke(config, errorContext);

    });

Fault header mapping

When processing dead-lettered messages, the transport maps native dead-letter properties to error forwarding headers when those headers are not already present:

  • DeadLetterSource -> NServiceBus.FailedQ
  • DeadLetterReason -> NServiceBus.ExceptionInfo.Message
  • DeadLetterErrorDescription -> NServiceBus.ExceptionInfo.StackTrace

This mapping helps tools such as ServiceControl and ServicePulse present failure information consistently.

Monitoring and operations

ServicePulse failed message monitoring tracks messages in the NServiceBus error queue. If endpoint failures are kept in native Azure Service Bus dead-letter queues without forwarding, those failures require Azure-native operational tooling.

Enable DLQ forwarding as described above when you want to centralized native dead lettering and failed-message handling.

Caveats

  • TransportTransactionMode.None uses receive-and-delete semantics, so dead-lettering actions cannot be performed in that mode. See transport transactions.
  • The transport truncates dead-letter reason and description to 1024 characters to reduce oversized message risk. Review Azure limits in Service Bus quotas.

Samples

Related Articles

  • Operational Scripting
    Explains how to create queues and topics with the Azure Service Bus transport using scripting.