Skip to content

Transport Configuration

Verified by tests

ServiceCollectionExtensionsBranchCoverageTests, ServiceCollectionExtensionsTests, RoutingBuilderExtensionsTests — library CI run #31657041675 (2026-08-13)

This guide covers configuring message transports in Whizbang, including environment-based switching between RabbitMQ (local development) and Azure Service Bus (production).

Transport Options

Transport Package Use Case
RabbitMQ SoftwareExtravaganza.Whizbang.Transports.RabbitMQ Local development, Aspire
Azure Service Bus SoftwareExtravaganza.Whizbang.Transports.AzureServiceBus Production, Azure deployment
None (local dispatch) Built-in Unit testing (no transport registration needed)

Environment-Based Transport Switching

Recommended Pattern: Runtime Configuration

var builder = WebApplication.CreateBuilder(args);

// Core Whizbang setup (storage; connection string resolved from configuration)
builder.Services
    .AddWhizbang()
    .WithEFCore<AppDbContext>("postgres")
    .WithDriver.Postgres;

// Transport switching based on configuration
var useRabbitMQ = builder.Configuration.GetValue<bool>("UseRabbitMQ");

if (useRabbitMQ) {
    // Local development with Aspire/RabbitMQ
    builder.Services.AddRabbitMQTransport(
        builder.Configuration.GetConnectionString("rabbitmq")!,
        options => {
            options.MaxChannels = 10;
            options.PrefetchCount = 200;
        });

    builder.Services.AddRabbitMQHealthChecks();
} else {
    // Production with Azure Service Bus
    builder.Services.AddAzureServiceBusTransport(
        builder.Configuration.GetConnectionString("servicebus")!,
        options => {
            options.MaxConcurrentCalls = 16;
            options.DefaultSubscriptionName = "order-service";
        });

    builder.Services.AddAzureServiceBusHealthChecks();
}

Configuration Files

appsettings.Development.json:

Configuration Files

{
  "UseRabbitMQ": true,
  "ConnectionStrings": {
    "postgres": "Host=localhost;Database=myapp;Username=postgres;Password=postgres",
    "rabbitmq": "amqp://guest:guest@localhost:5672"
  }
}

appsettings.Production.json:

Configuration Files (2)

{
  "UseRabbitMQ": false,
  "ConnectionStrings": {
    "postgres": "Host=myapp.postgres.database.azure.com;Database=myapp;...",
    "servicebus": "Endpoint=sb://myapp.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=..."
  }
}

Note: Transport options (RabbitMQOptions, AzureServiceBusOptions) are configured through the callback passed to the Add*Transport registration, not bound from configuration sections. Topics, exchanges, and subscriptions are auto-provisioned from your routing configuration (see Message Routing below) — there is no manual exchange/topic naming option on the transport itself.

RabbitMQ Configuration

Basic Setup

Basic Setup

builder.Services.AddRabbitMQTransport(
    builder.Configuration.GetConnectionString("rabbitmq")!);

Advanced Configuration

Advanced Configuration

builder.Services.AddRabbitMQTransport(
    builder.Configuration.GetConnectionString("rabbitmq")!,
    options => {
        // Channel pooling
        options.MaxChannels = 10;

        // Consumer configuration
        options.PrefetchCount = 200;
        options.EnableSingleActiveConsumer = false;

        // Delivery / dead-lettering
        options.MaxDeliveryAttempts = 10;
        options.AutoDeclareDeadLetterExchange = true;

        // Connection retry (initial attempts, then optionally indefinite)
        options.InitialRetryAttempts = 5;
        options.InitialRetryDelay = TimeSpan.FromSeconds(1);
        options.MaxRetryDelay = TimeSpan.FromSeconds(120);
        options.BackoffMultiplier = 2.0;
        options.RetryIndefinitely = true;
    });

// Add health checks (registers the Whizbang RabbitMQ health check)
builder.Services.AddRabbitMQHealthChecks();

Aspire Integration

Aspire Integration

// In AppHost project
var rabbitmq = builder.AddRabbitMQ("rabbitmq")
    .WithManagementPlugin();

var api = builder.AddProject<Projects.MyApp_API>("api")
    .WithReference(rabbitmq);

Aspire Integration (2)

// In API project - Aspire injects the connection string into configuration
builder.Services.AddRabbitMQTransport(
    builder.Configuration.GetConnectionString("rabbitmq")!);

Azure Service Bus Configuration

Basic Setup

Basic Setup (2)

builder.Services.AddAzureServiceBusTransport(
    builder.Configuration.GetConnectionString("servicebus")!);

Advanced Configuration

Advanced Configuration (2)

builder.Services.AddAzureServiceBusTransport(
    builder.Configuration.GetConnectionString("servicebus")!,
    options => {
        // Infrastructure auto-provisioning (topics + subscriptions)
        options.AutoProvisionInfrastructure = true;
        options.DefaultSubscriptionName = "order-service";

        // Consumer concurrency
        options.MaxConcurrentCalls = 16;
        options.PrefetchCount = 50;

        // Session handling (per-stream ordered processing)
        options.EnableSessions = true;
        options.MaxConcurrentSessions = 200;
        options.SessionIdleTimeout = TimeSpan.FromSeconds(1);

        // Delivery / locks
        options.MaxDeliveryAttempts = 10;
        options.MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(5);

        // Connection retry
        options.InitialRetryAttempts = 5;
        options.InitialRetryDelay = TimeSpan.FromSeconds(1);
        options.RetryIndefinitely = true;
    });

// Add health checks (registers the Whizbang Service Bus health check)
builder.Services.AddAzureServiceBusHealthChecks();

Managed Identity Authentication

AddAzureServiceBusTransport takes a connection string, but it reuses any ServiceBusClient you have already registered. To authenticate with a managed identity, register the client yourself first:

Managed Identity Authentication

// Pre-register a ServiceBusClient using DefaultAzureCredential;
// the transport registration detects and reuses it.
builder.Services.AddSingleton(new ServiceBusClient(
    "myapp.servicebus.windows.net",
    new DefaultAzureCredential()));

builder.Services.AddAzureServiceBusTransport(
    builder.Configuration.GetConnectionString("servicebus")!);

Migrating from Wolverine Transports

Wolverine RabbitMQ

Wolverine RabbitMQ

// Wolverine
builder.Host.UseWolverine(opts => {
    opts.UseRabbitMq("amqp://guest:guest@localhost:5672")
        .UseConventionalRouting()
        .UseDurableOutbox();
});

Wolverine RabbitMQ (2)

// Whizbang
// - Outbox is built-in to Whizbang Core (no UseDurableOutbox equivalent needed)
// - Exchanges are auto-provisioned from routing configuration
builder.Services.AddRabbitMQTransport("amqp://guest:guest@localhost:5672");

Wolverine Azure Service Bus

Wolverine Azure Service Bus

// Wolverine
builder.Host.UseWolverine(opts => {
    opts.UseAzureServiceBus(connectionString)
        .UseTopicsAndSubscriptions()
        .UseDurableOutbox();
});

Wolverine Azure Service Bus (2)

// Whizbang
// Topics and subscriptions are auto-provisioned from routing configuration
builder.Services.AddAzureServiceBusTransport(connectionString);

Message Routing

Routing is namespace-based: events publish to topics derived from their namespace, and commands route point-to-point through a shared inbox topic. Configure it with WithRouting on the builder chain:

Namespace-Based Routing

builder.Services
    .AddWhizbang()
    .WithRouting(routing => {
        // Domains this service owns (its own command/event namespaces)
        routing.OwnDomains("myapp.orders.commands", "myapp.orders.events");

        // Or derive the namespace from a marker type
        routing.OwnNamespaceOf<OrderCreated>();

        // Event namespaces published by OTHER services that this service consumes
        routing.SubscribeTo("myapp.payments.events");
        routing.SubscribeToNamespaceOf<PaymentProcessed>();

        // Commands arrive on a shared inbox topic (the default strategy)
        routing.Inbox.UseSharedTopic("whizbang.inbox");
    })
    .AddTransportConsumer(); // Auto-generates transport subscriptions from routing config

Per-message-type topic overrides and SQL-style subscription filters are not part of the routing API — topics come from namespaces, and receive-side filtering happens automatically (messages with no local receptor or perspective are discarded at the receive boundary).

Testing Configuration

No Transport Needed for Local Tests

For unit and in-memory integration tests, skip transport registration entirely — local dispatch and the EF Core InMemory driver cover the full pipeline:

In-Memory Testing Without a Transport

public static class TestServices {
    public static ServiceProvider Build() {
        var services = new ServiceCollection();

        // In-memory storage; no AddRabbitMQTransport/AddAzureServiceBusTransport call
        services
            .AddWhizbang()
            .WithEFCore<TestDbContext>()
            .WithDriver.InMemory;

        return services.BuildServiceProvider();
    }
}

Integration Test with TestContainers

Whizbang uses TUnit (not xUnit), so container lifecycle hooks use [Before(Test)]/[After(Test)] instead of IAsyncLifetime:

Integration Test with TestContainers

public class RabbitMqTransportTests {
    private RabbitMqContainer _rabbitMq = null!;

    [Before(Test)]
    public async Task SetupAsync() {
        _rabbitMq = new RabbitMqBuilder()
            .WithImage("rabbitmq:3-management")
            .Build();

        await _rabbitMq.StartAsync();
    }

    [After(Test)]
    public async Task TeardownAsync() {
        await _rabbitMq.DisposeAsync();
    }

    private ServiceProvider BuildServices() {
        var services = new ServiceCollection();
        services.AddRabbitMQTransport(_rabbitMq.GetConnectionString());
        return services.BuildServiceProvider();
    }
}

Migration Checklist

  • [ ] Add SoftwareExtravaganza.Whizbang.Transports.RabbitMQ and/or SoftwareExtravaganza.Whizbang.Transports.AzureServiceBus packages
  • [ ] Configure environment-based transport switching
  • [ ] Set up appsettings.Development.json for RabbitMQ
  • [ ] Set up appsettings.Production.json for Azure Service Bus
  • [ ] Add AddRabbitMQHealthChecks() / AddAzureServiceBusHealthChecks()
  • [ ] Update Aspire integration (if using)
  • [ ] Configure namespace-based routing with WithRouting(...) and AddTransportConsumer()
  • [ ] Update in-memory integration tests to use .WithDriver.InMemory (no transport registration)

Previous: Event Store Migration | Next: Outbox Migration