Skip to content

Configuration

Builder Pattern

csharp
var messaging = builder.Services.AddMessaging(options =>
{
    // Connection
    options.ConnectionString = "...";
    // OR
    options.FullyQualifiedNamespace = "mybus.servicebus.windows.net";
    options.Credential = new DefaultAzureCredential();
    
    // Defaults for all processors
    options.Defaults.MaxConcurrentCalls = 16;
    options.Defaults.AutoComplete = true;
    options.Defaults.PrefetchCount = 0;
    options.Defaults.MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(5);
    
    // Serialization
    options.Serializer = new SystemTextJsonSerializer(new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    });
    
    // Retry policy
    options.RetryOptions.MaxRetries = 3;
    options.RetryOptions.Delay = TimeSpan.FromSeconds(1);
    options.RetryOptions.MaxDelay = TimeSpan.FromSeconds(30);
    options.RetryOptions.Mode = ServiceBusRetryMode.Exponential;
});

Handler Configuration

Handler options (concurrency, prefetch, auto-complete) are configured via a delegate on the handler method. Error handling is chained on the returned builder:

csharp
messaging.MapQueue<OrderCreated>("orders")
    .MapHandler(
        options => options
            .WithConcurrency(maxConcurrentCalls: 10)
            .WithPrefetch(count: 20)
            .WithAutoComplete(enabled: false)
            .WithAutoLockRenewal(duration: TimeSpan.FromMinutes(10))
            .WithDeadLetterOnFailure(reason: "ProcessingFailed"),
        handler)
    .MapErrorHandler(async (ex, ctx, ct) =>
    {
        // Custom error handling
        await ctx.DeadLetterAsync("ProcessingFailed", ex.Message, ct);
    });

// Process dead letter queue
messaging.MapQueue<OrderCreated>("orders")
    .MapDeadLetterHandler(handler);

Infrastructure Topology Introspection

While this library doesn't create or manage infrastructure, it does know what infrastructure it requires. Exposing this information helps with:

  • Validation: Check at startup that required entities exist
  • Testing: Verify handler registrations are wired correctly
  • Documentation: Generate infrastructure requirements
  • IaC Generation: Feed into Bicep/Terraform/Pulumi templates

API

Inject IServiceBusTopology to inspect the registered queues, topics, and subscriptions at runtime. Each requirement record includes the entity name, whether sessions are required, the message type, and whether it's a dead-letter processor.

Usage

csharp
var topology = app.Services.GetRequiredService<IServiceBusTopology>();

// Log what we need
foreach (var queue in topology.Queues)
{
    logger.LogInformation("Requires queue: {Queue} (sessions: {Sessions})", 
        queue.QueueName, queue.RequiresSessions);
}

// Validate at startup
foreach (var queue in topology.Queues)
{
    if (!await adminClient.QueueExistsAsync(queue.QueueName))
        throw new InvalidOperationException($"Required queue '{queue.QueueName}' does not exist");
}

Built-in Validation (Optional)

csharp
var messaging = builder.Services.AddMessaging(options =>
{
    options.ValidateTopologyOnStartup = true; // Throws if entities don't exist
});

TIP

Topology validation checks that entities exist and that their configuration matches what the library expects. For example, if you register a session handler via MapSessionHandler, validation will fail if the Azure queue does not have RequiresSession = true. Key properties validated:

Library FeatureRequired Azure Entity Property
MapSessionHandler / MapSessionSubscriptionRequiresSession = true (immutable - must recreate entity)
MapDeadLetterHandlerEntity must exist (dead-letter sub-queue is automatic)
Request/reply (RequestAsync)Reply queue with RequiresSession = true
High-concurrency handlersConsider increasing LockDuration (default 30s, max 5min)
WithDeadLetterOnFailureDeadLetteringOnMessageExpiration or MaxDeliveryCount on entity

Entity Settings

QueueBuilder<T> and TopicBuilder<T> expose fluent methods to declare entity-level settings. These settings are captured in the topology and surface through IServiceBusTopology requirement records, enabling export to emulator config, Bicep, ARM, or runtime provisioning.

csharp
// Queue with custom settings
messaging.MapQueue<OrderCreated>("orders")
    .WithMaxDeliveryCount(5)
    .WithLockDuration(TimeSpan.FromMinutes(2))
    .WithDefaultMessageTimeToLive(TimeSpan.FromHours(24));

// Session queue
messaging.MapQueue<GatewayCommand>("gateway-commands")
    .WithRequiresSession();

// Topic settings apply to subscriptions
messaging.MapTopic<DomainEvent>("events")
    .WithMaxDeliveryCount(3)
    .MapSubscription("audit", handler);

Settings are nullable - null means "use platform default". The requirement records (QueueRequirement, SubscriptionRequirement) expose these as int? MaxDeliveryCount, TimeSpan? LockDuration, and TimeSpan? DefaultMessageTimeToLive.

MethodApplies toDefault (Azure)
WithMaxDeliveryCount(int)Queue, Subscription10
WithLockDuration(TimeSpan)Queue, Subscription30 seconds
WithDefaultMessageTimeToLive(TimeSpan)Queue, SubscriptionMax (infinite)
WithRequiresSession()Queue onlyfalse

Topology Export

IServiceBusTopology includes an extension method for exporting to the Azure Service Bus Emulator JSON format. This is useful for integration testing - no separate config file or builder needed:

csharp
var topology = app.Services.GetRequiredService<IServiceBusTopology>();

// Export to emulator JSON string
string json = topology.ToEmulatorConfigJson();

// Export to UTF-8 bytes (for Testcontainers resource mapping)
byte[] bytes = topology.ToEmulatorConfigBytes();

Null entity settings are filled with sensible defaults for the emulator: MaxDeliveryCount = 10, LockDuration = PT1M, DefaultMessageTimeToLive = PT1H.

See Testing for a complete example of using topology export with Testcontainers.

Topology Declarations (Sender-Only Services)

With the entity-first API, sender-only entities are declared simply by calling MapQueue<T> or MapTopic<T> without attaching a handler. This automatically registers both topology requirements and a typed IMessageSender<T>:

csharp
var messaging = builder.Services.AddMessaging(options =>
{
    options.ConnectionString = "...";
});

// Sender-only - no handler attached, but topology is tracked
// and IMessageSender<OrderCreated> is registered in DI
messaging.MapQueue<OrderCreated>("orders");
messaging.MapTopic<DomainEvent>("events");

This ensures IServiceBusTopology includes entities used for sending, not just receiving.