BunnyMQ 0.1.6
dotnet add package BunnyMQ --version 0.1.6
NuGet\Install-Package BunnyMQ -Version 0.1.6
<PackageReference Include="BunnyMQ" Version="0.1.6" />
<PackageVersion Include="BunnyMQ" Version="0.1.6" />
<PackageReference Include="BunnyMQ" />
paket add BunnyMQ --version 0.1.6
#r "nuget: BunnyMQ, 0.1.6"
#:package BunnyMQ@0.1.6
#addin nuget:?package=BunnyMQ&version=0.1.6
#tool nuget:?package=BunnyMQ&version=0.1.6
Bunny
Controller-style RabbitMQ consumer/publisher for .NET 10. Route AMQP messages to handler methods with attributes — like ASP.NET controllers, but for the bus.
[Exchange("orders")]
public class OrderHandler(IOrderService orders, ILogger<OrderHandler> logger) : Bunny.EventHandler
{
[Topic("order.{id:guid}.created")]
public async Task<AckResult> OnCreated(Guid id, [FromBody] OrderCreatedDto dto, CancellationToken ct)
{
logger.LogInformation("Order {Id} from {RoutingKey}", id, RoutingKey);
return await orders.TryHandle(dto, ct) ? Ack() : Nack(requeue: true);
}
}
Why
The .NET ecosystem has plenty of RabbitMQ libraries — MassTransit, EasyNetQ, Rebus, raw RabbitMQ.Client. Each comes with its own mental model: sagas, consumers, message contexts, handlers, pipeline behaviors. Every team picks one and then reinvents how to organize code around it.
Bunny is opinionated about exactly that one thing: organize event handling the same way you organize REST controllers.
// REST controller — every .NET developer knows this shape
[Route("orders")]
public class OrdersController(IOrderService orders) : ControllerBase
{
[HttpPost("{id:guid}/cancel")]
public Task Cancel(Guid id, [FromBody] CancelDto dto) => orders.Cancel(id, dto);
}
// Bunny handler — same shape, AMQP routing key instead of an HTTP route
[Exchange("orders")]
public class OrderHandler(IOrderService orders) : Bunny.EventHandler
{
[Topic("order.{id:guid}.cancel")]
public Task Cancel(Guid id, [FromBody] CancelDto dto, CancellationToken ct) => orders.Cancel(id, dto, ct);
}
If you've ever written an ASP.NET controller, you already know Bunny. Class = group of related handlers. Method = one handler. Attribute = route. Return value = response. No new concepts to learn — even a developer who's never touched RabbitMQ can read a handler and understand what it does on the first try.
Trade-off: Bunny doesn't compete on features with MassTransit & co. No built-in sagas, no Outbox pattern, no cross-broker abstraction, no scheduling. It does one thing: attribute-routed AMQP handlers that look like REST controllers. Need more than that and you'll outgrow Bunny.
Install
dotnet add package BunnyMQ
Code still references the Bunny namespace (and assembly) — only the NuGet listing name is BunnyMQ (the simpler Bunny package id is taken on nuget.org by an unrelated project).
Or as a project reference during local development:
<ProjectReference Include="..\..\Libs\Bunny\Bunny\Bunny.csproj" />
Quick start
// Program.cs
builder.Services.AddBunny(b => b
.ScanAssembly(typeof(Program).Assembly)
.DeclareExchange("audit")); // optional: publish-only exchanges
// appsettings.json
{
"Bunny": {
"HostName": "rabbitmq",
"Port": 5672,
"UserName": "guest",
"Password": "guest",
"VirtualHost": "/"
}
}
Write a handler:
using EventHandler = Bunny.EventHandler; // avoids clash with System.EventHandler delegate
[Exchange("orders")]
public class OrderHandler(IOrderService orders) : EventHandler
{
[Topic("order.{id:guid}.created", Queue = "orders.created", Prefetch = 20)]
public Task OnCreated(Guid id, [FromBody] OrderDto dto, CancellationToken ct) => orders.HandleAsync(id, dto, ct);
}
Publish from anywhere:
public class CheckoutService(IBunnyPublisher bus)
{
public Task Emit(Order o, CancellationToken ct)
=> bus.PublishAsync("orders", $"order.{o.Id}.created", o, ct);
}
If a service only publishes (REST API, background job) and never consumes, use the lightweight AddBunnyPublisher — no scanning, no consumer channels:
builder.Services.AddBunnyPublisher(b => b
.DeclareExchange("orders")
.DeclareExchange("audit"));
Docs
- Getting started — install, configure, write your first handler step-by-step
- Examples & recipes — route params, publishing from handlers, early ack, custom serializers, poison messages
Repo layout
Bunny/ <-- repo root
├── Bunny.sln
├── README.md
├── docs/
├── Bunny/ <-- library project
│ └── Bunny.csproj
└── Bunny.Tests/ <-- xUnit + NSubstitute + FluentAssertions
└── Bunny.Tests.csproj
Build & test from the root:
dotnet build Bunny.sln
dotnet test Bunny.sln
Features
- Attribute routing:
[Exchange]on class,[Topic("order.{id:guid}.created")]on methods - Typed route parameter binding:
int,long,guid,string,bool,double,float - Scoped DI per message —
DbContext& friends just work - Async pipeline end-to-end (RabbitMQ.Client v7)
- Return-value ack:
return Ack();/Nack(requeue: true)/Reject()— orvoidfor implicit ack - Early reply:
AckNowAsync()/NackNowAsync()/RejectNowAsync()when you want to free the prefetch slot before finishing - Pluggable serialization (
IBunnySerializer, STJ default) - Per-topic prefetch & requeue-on-error
- Configurable connection (
IOptions<BunnyOptions>)
Status
Alpha. Public API may change. No DLX / dead-letter helpers yet (configure on the queue directly). No retry policies. No publisher channel pool (single shared channel + semaphore).
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- RabbitMQ.Client (>= 7.2.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.