RabbitMQ in .NET Aspire: Messaging Between Services

When your distributed application needs asynchronous communication between services, RabbitMQ is a proven choice. Aspire's RabbitMQ component handles the broker container, connection wiring, and telemetry so you can focus on your messaging patterns.

Adding RabbitMQ to the AppHost

Declare RabbitMQ in your AppHost:

Program.cs (AppHost)
var builder = DistributedApplication.CreateBuilder(args);

var messaging = builder.AddRabbitMQ("messaging")
    .WithManagementPlugin();

var orderApi = builder.AddProject<Projects.OrderApi>("order-api")
    .WithReference(messaging);

var orderProcessor = builder.AddProject<Projects.OrderProcessor>("order-processor")
    .WithReference(messaging);

builder.Build().Run();

The WithManagementPlugin call enables the RabbitMQ management UI, accessible via the endpoint shown in the Aspire dashboard. This gives you a web interface to inspect queues, exchanges, and message rates during development.

Consuming RabbitMQ in Your Services

Install the Aspire RabbitMQ component:

dotnet add package Aspire.RabbitMQ.Client

Register the connection factory:

Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.AddRabbitMQClient("messaging");

You can now inject IConnection and create channels:

Example.cs
public class OrderPublisher
{
    private readonly IConnection _connection;

    public OrderPublisher(IConnection connection)
    {
        _connection = connection;
    }

    public async Task PublishOrderCreatedAsync(OrderCreatedEvent orderEvent)
    {
        using var channel = await _connection.CreateChannelAsync();

        await channel.ExchangeDeclareAsync(
            exchange: "orders",
            type: ExchangeType.Topic,
            durable: true);

        var body = JsonSerializer.SerializeToUtf8Bytes(orderEvent);

        await channel.BasicPublishAsync(
            exchange: "orders",
            routingKey: "order.created",
            body: body);
    }
}

Building a Consumer

For the consuming service, the pattern is similar. Register the connection and create a consumer using a background service:

Example.cs
public class OrderProcessorWorker : BackgroundService
{
    private readonly IConnection _connection;
    private readonly ILogger<OrderProcessorWorker> _logger;

    public OrderProcessorWorker(IConnection connection,
        ILogger<OrderProcessorWorker> logger)
    {
        _connection = connection;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var channel = await _connection.CreateChannelAsync();

        await channel.ExchangeDeclareAsync("orders", ExchangeType.Topic, durable: true);
        var queueResult = await channel.QueueDeclareAsync(
            queue: "order-processing",
            durable: true,
            exclusive: false,
            autoDelete: false);

        await channel.QueueBindAsync("order-processing", "orders", "order.created");

        var consumer = new AsyncEventingBasicConsumer(channel);
        consumer.ReceivedAsync += async (_, ea) =>
        {
            var order = JsonSerializer.Deserialize<OrderCreatedEvent>(ea.Body.Span);
            _logger.LogInformation("Processing order {OrderId}", order?.OrderId);

            // Process the order...

            await channel.BasicAckAsync(ea.DeliveryTag, multiple: false);
        };

        await channel.BasicConsumeAsync("order-processing", autoAck: false,
            consumer: consumer);

        await Task.Delay(Timeout.Infinite, stoppingToken);
    }
}

Using MassTransit with Aspire

If you prefer a higher-level messaging abstraction, MassTransit works well with Aspire. The AppHost setup remains the same — you still declare RabbitMQ with AddRabbitMQ. On the consumer side:

dotnet add package MassTransit.RabbitMQ
Example.cs
builder.Services.AddMassTransit(x =>
{
    x.AddConsumer<OrderCreatedConsumer>();

    x.UsingRabbitMq((context, cfg) =>
    {
        var connectionString = builder.Configuration.GetConnectionString("messaging");
        cfg.Host(new Uri(connectionString!));
        cfg.ConfigureEndpoints(context);
    });
});

The consumer becomes a simple class:

Example.cs
public class OrderCreatedConsumer : IConsumer<OrderCreatedEvent>
{
    private readonly ILogger<OrderCreatedConsumer> _logger;

    public OrderCreatedConsumer(ILogger<OrderCreatedConsumer> logger)
    {
        _logger = logger;
    }

    public async Task Consume(ConsumeContext<OrderCreatedEvent> context)
    {
        _logger.LogInformation("Processing order {OrderId}",
            context.Message.OrderId);

        // Process the order...
    }
}

Health Checks and Telemetry

The Aspire RabbitMQ component registers a health check that verifies the broker connection. If RabbitMQ becomes unreachable, the health check reports unhealthy.

RabbitMQ operations appear in your distributed traces automatically. You can see publish and consume operations in the Aspire dashboard, including message routing keys and queue names.

Configuration

Customise the connection through settings:

Example.cs
builder.AddRabbitMQClient("messaging", settings =>
{
    settings.DisableHealthChecks = false;
    settings.DisableTracing = false;
    settings.MaxConnectRetryCount = 5;
});

From declaring the broker to publishing your first message, Aspire reduces the setup to a few lines of code. The container management, connection wiring, health checks, and telemetry all come for free.