**MassTransit **in ASP.NET Core: A Practical Guide to Event-Driven .NET
Intro
Event-driven architectures help teams decouple services, scale independently, and handle failures gracefully. MassTransit is a mature, open-source library that makes message-based workflows in .NET straightforward. This guide shows a minimal but production-ready setup in ASP.NET Core with RabbitMQ.
1) Install packages
From your Web API project:
dotnet add package MassTransit
dotnet add package MassTransit.RabbitMQ
(For Azure Service Bus use MassTransit.Azure.ServiceBus.Core instead.)
2) Define a message contract
Contracts should be versionable and live in a shared project.
public interface SubmitOrder
{
Guid OrderId { get; }
string CustomerId { get; }
DateTime Timestamp { get; }
}
3) Create a consumer
using MassTransit;
public sealed class SubmitOrderConsumer : IConsumer<SubmitOrder>
{
public async Task Consume(ConsumeContext<SubmitOrder> context)
{
var msg = context.Message;
// Your domain logic here (idempotent!)
Console.WriteLine($"Received SubmitOrder {msg.OrderId} for {msg.CustomerId}");
await Task.CompletedTask;
}
}
4) Configure MassTransit in Program.cs (.NET 9 minimal hosting)
RabbitMQ example with retries and health checks.
using MassTransit;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMassTransit(cfg =>
{
cfg.SetKebabCaseEndpointNameFormatter();
cfg.AddConsumer<SubmitOrderConsumer>(c =>
{
// optional: configure consumer-level retry, etc.
});
cfg.UsingRabbitMq((context, bus) =>
{
bus.Host("rabbitmq", h =>
{
h.Username("guest");
h.Password("guest");
});
bus.ReceiveEndpoint("submit-order-queue", e =>
{
e.ConfigureConsumeTopology = false; // explicit is safer for versioning
e.ConfigureConsumer<SubmitOrderConsumer>(context);
// Robustness: retry with jitter + immediate faults to _error queue if exhausted
e.UseMessageRetry(r => r.Interval(3, TimeSpan.FromSeconds(5)));
e.PrefetchCount = 16;
e.ConcurrentMessageLimit = 8;
});
});
});
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapHealthChecks("/health");
app.MapGet("/", () => "OK");
app.Run();
Docker tip
If you run RabbitMQ locally via Docker:
docker run -d --hostname rabbit \
-p 5672:5672 -p 15672:15672 \
--name rabbitmq rabbitmq:3-management
UI is at http://localhost:15672 (guest/guest).
5) Publish a message (from a Controller or Service)
Inject **IPublishEndpoint **for pub/sub or **ISendEndpointProvider **for point-to-point.
using MassTransit;
public sealed class OrderAppService
{
private readonly IPublishEndpoint _publish;
public OrderAppService(IPublishEndpoint publish) => _publish = publish;
public Task SubmitAsync(Guid orderId, string customerId)
=> _publish.Publish<SubmitOrder>(new
{
OrderId = orderId,
CustomerId = customerId,
Timestamp = DateTime.UtcNow
});
}
Or via an endpoint (send to a specific queue):
public sealed class OrderSender
{
private readonly ISendEndpointProvider _send;
public OrderSender(ISendEndpointProvider send) => _send = send;
public async Task SendAsync(Guid orderId, string customerId)
{
var endpoint = await _send.GetSendEndpoint(new Uri("queue:submit-order-queue"));
await endpoint.Send<SubmitOrder>(new { OrderId = orderId, CustomerId = customerId, Timestamp = DateTime.UtcNow });
}
}
6) Error handling, retries, and observability
- Retries: use UseMessageRetry on endpoints or the bus. Prefer bounded retries with intervals or exponential backoff.
- Poison messages: failed messages after retries land in _error queues automatically.
- Health checks: expose /health and rely on container orchestration to restart unhealthy pods.
- Idempotency: make consumers safe to reprocess (e.g., check a processed table or use dedup keys).
7) (Optional) Outbox & transactions
If you publish events within a DB transaction, consider an outbox pattern (MassTransit integrates with EFCore Outbox) to avoid dual-write issues and ensure at-least-once delivery without duplicates.
8) Azure Service Bus variant (quick sketch)
Swap the transport:
builder.Services.AddMassTransit(cfg =>
{
cfg.AddConsumer<SubmitOrderConsumer>();
cfg.UsingAzureServiceBus((context, bus) =>
{
bus.Host(builder.Configuration["ASB_CONNECTION"]!);
bus.SubscriptionEndpoint<SubmitOrder>("submit-order-sub", e =>
{
e.ConfigureConsumer<SubmitOrderConsumer>(context);
e.MaxConcurrentCalls = 8;
});
});
});
Conclusion
MassTransit keeps the happy path simple while giving you the tools for serious systems: retries, sagas, scheduling, observability, and transport flexibility. Start minimal, add policies as you learn your failure modes, and keep consumers idempotent.
Top comments (0)