Table of Contents

RabbitMQ Transport

RabbitMQ is BareWire's reference transport (BareWire.Transport.RabbitMQ) and the broker most of this site is written against. It maps BareWire's core concepts directly onto AMQP 0-9-1: exchanges, queues, and bindings declared as manual topology, plus consistent-hash and single-active-consumer for per-key ordering, native dead-letter exchanges, and publish-style request/response.

Because RabbitMQ is the default throughout the documentation, this page is a transport-specific reference — registration, connection and TLS, settlement, and a map to the feature guides — rather than a re-explanation of every feature. The deep guides are linked at the end of each section.

Registration

As with every BareWire transport, you register the core engine and the RabbitMQ transport together. See Configuration for the full rationale behind the layering.

The BareWire.RabbitMQ bundle depends on both the core and the transport and exposes one method, AddBareWireWithRabbitMq. Configure the transport in the first delegate and (optionally) the bus in the second:

builder.Services.AddBareWireWithRabbitMq(
    transport =>
    {
        transport.Host("amqp://guest:guest@localhost:5672/");
        transport.ConfigureTopology(t => { /* exchanges, queues, bindings */ });
        transport.ReceiveEndpoint("orders", e => e.Consumer<OrderConsumer, OrderCreated>());
    },
    bus =>
    {
        bus.AddConsumer<OrderConsumer>();
        // serializers, middleware...
    });

The bus delegate is optional — omit it when transport defaults are enough:

builder.Services.AddBareWireWithRabbitMq(transport => transport.Host("amqp://localhost"));

2. Two calls — core and transport registered separately

AddBareWireWithRabbitMq is sugar over the explicit pair. Use the two-call form when you reference the core and transport packages separately, or when an application registers more than one transport (a bundle call registers the core internally, so two bundle calls would register it twice):

builder.Services.AddBareWireRabbitMq(transport => transport.Host("amqp://guest:guest@localhost:5672/"));
builder.Services.AddBareWire(bus => bus.AddConsumer<OrderConsumer>());

Deprecated: configuring the transport with cfg.UseRabbitMQ(...) inside the AddBareWire delegate is an obsolete no-op — any host/credentials passed to it are ignored. Configure the transport through AddBareWireRabbitMq or the bundle. See Configuration.

Connection

Point the transport at the broker with Host. The connection string is typically injected via Aspire or configuration:

// Via Aspire (automatic) — registers the RabbitMQ client connection
builder.AddRabbitMQClient("rabbitmq");

// Via connection string
transport.Host("amqp://guest:guest@localhost:5672/");

Credentials can also be supplied (and overridden) through the host configurator rather than embedded in the URI — useful when the username/password come from a secret store:

transport.Host("amqp://broker.internal:5672/", h =>
{
    h.Username(username);
    h.Password(password);   // never logged or echoed in diagnostics
});

TLS and mutual TLS

For an encrypted connection, use the amqps:// scheme and configure TLS through the host configurator's UseTls block. UseTls exposes an ITlsConfigurator:

Method Meaning
WithCertificate(path, password?) Client certificate file (PFX or PEM) and an optional private-key passphrase (the passphrase is never logged).
WithMutualAuthentication() Enables mTLS — the client presents its certificate to the broker during the handshake.
WithServerValidation(SslPolicyErrors) The set of SslPolicyErrors tolerated during server-certificate validation. Defaults to SslPolicyErrors.None (strict). In production always use None — relax it only to accept self-signed certificates in test environments.
using System.Net.Security;

// Server-authenticated TLS
transport.Host("amqps://broker.internal:5671/", h =>
{
    h.Username(username);
    h.Password(password);
    h.UseTls(tls => tls.WithServerValidation(SslPolicyErrors.None));
});

// Mutual TLS (client certificate)
transport.Host("amqps://broker.internal:5671/", h =>
{
    h.UseTls(tls => tls
        .WithCertificate("/etc/secrets/client.pfx", certPassword)
        .WithMutualAuthentication()
        .WithServerValidation(SslPolicyErrors.None));
});

Receive endpoints

Each receive endpoint binds a consumer (or several) to a queue and carries its own concurrency and retry settings:

transport.ReceiveEndpoint("orders", e =>
{
    e.PrefetchCount = 16;            // broker-level prefetch
    e.ConcurrentMessageLimit = 8;    // in-flight concurrency
    e.RetryCount = 3;                // retry attempts before DLQ
    e.RetryInterval = TimeSpan.FromSeconds(1);

    e.Consumer<OrderConsumer, OrderCreated>();
});

An endpoint can also host a raw consumer (e.RawConsumer<T>()), a saga state machine (e.StateMachineSaga<T>()), multiple typed consumers, consume-time routing-key dispatch, and per-key ordering. See Publishing and Consuming, Consumer Routing Keys, and Per-Key Consumer Ordering.

Settlement and dead-lettering

When a consumer succeeds the message is acknowledged; when it throws, BareWire retries up to RetryCount and then negatively settles the delivery. With a dead-letter exchange declared on the queue, the failed message is routed to the DLX rather than discarded — without one, a rejected message is permanently lost (and BareWire logs a warning). Always configure a DLX on production queues. See Retry and Dead Letter Queues.

Routing semantics

A publisher confirm tells you the broker accepted a publication — not that it routed it to a queue. By default BareWire publishes with the AMQP mandatory flag off, so a message the broker accepts but cannot route to any queue (topology drift, a missing binding or queue, a wrong routing key) is silently dropped while SendResult.IsConfirmed still reports true. This is at-most-once routing: fast, but a misconfigured topology loses messages without a signal.

Enable guaranteed routing to surface unroutable publications instead of dropping them silently:

wire.UseRabbitMq(rmq =>
{
    rmq.GuaranteedRouting();   // opt-in; default is off
    // ... host, topology, endpoints ...
});

With it enabled, BareWire publishes mandatory and detects an unroutable message via the broker's return, mapping it to SendResult.IsConfirmed == false (and logging a warning). The default-off behaviour is unchanged — routable publishes are unaffected (the channel already awaits a per-message publisher confirm, so there is no extra round-trip), and there is no per-message allocation on the send path.

Where it fails closed. The negative confirm only changes the outcome for code that inspects SendResult — chiefly the transactional outbox dispatcher, which treats IsConfirmed == false as non-delivery and retries (the row stays claimed instead of being marked delivered). The direct IBus.PublishAsync / ISendEndpoint.SendAsync path is fire-and-forget: the caller never receives a SendResult, and the background publisher does not redeliver on a negative confirm. On that path guaranteed routing turns a silent drop into an observable one (the warning log) but does not by itself make direct publishing at-least-once. For at-least-once delivery against topology drift, publish through the outbox with this option enabled.

Header trust boundary

Headers prefixed BW- are reserved for BareWire and the transport itself.

  • Send side — an unmapped BW-* header in any letter case (BW-Forged, bw-forged, Bw-Forged) is not published; it never reaches the broker.
  • Receive side — an unmapped raw AMQP header whose name starts with BW- in any letter case is dropped before the consumer sees it, with two exceptions the transport itself stamps or reads:
    • BW-MappingEpoch — the diagnostic mapping-epoch marker the transport stamps on the wire itself. A forged value only affects diagnostics — it can trigger a spurious warning and overwrite the remembered lane epoch, masking or faking a real re-map — never routing, dispatch, or settlement.
    • BW-MessageType — accepted only while the AMQP type property is empty; the type property always wins.
  • Routing metadata is always transport-authoritative — BW-Exchange, BW-RoutingKey, and BW-ConsumerChannelId are stamped by the transport from the delivery itself, never from a raw header the publisher supplied.
  • What the publisher still controls — the message type (via the AMQP type property, or a raw BW-MessageType header while type is empty), message-id, correlation-id, reply-to, content-type, traceparent, and the routing key value used at publish time. For untrusted publishers, restrict access with broker permissions or an authorization middleware rather than relying on the header filter alone.
  • The in-memory transport applies the same reserved-prefix rule, with the same nuance — both transports strip an unmapped BW-* header the publisher sets, and both let BW-MessageType through. RabbitMQ narrows that to the case where the AMQP type property is empty, and additionally lets you carry any other BW-* header through the broker with an explicit mapping — the in-memory transport has no mapping mechanism, so it never round-trips one.

Carry your own BW-* header through the broker in both directions with an explicit mapping:

transport.ConfigureHeaderMapping(headers =>
{
    headers.MapHeader("BW-TenantId", "BW-TenantId");
});

Behavior change: an unmapped raw BW-* header a publisher sets no longer reaches consumers. To keep one, map it explicitly as shown above. For the message type, set the AMQP type property or configure MapMessageType(...) — a raw BW-MessageType header keeps working only while the type property is empty.

Feature map

RabbitMQ-specific behaviour is documented across these guides:

Topic Guide
Transport registration paths and layering Configuration
Exchanges, queues, bindings, IQueueConfigurator, DLX Topology
Publish/subscribe, request/response, raw messages, per-type send routing Publishing and Consuming
Consume-time routing-key dispatch on a shared queue Consumer Routing Keys
Single-active-consumer / consistent-hash per-key ordering Per-Key Consumer Ordering
Retry policies and dead-letter exchanges Retry and Dead Letter Queues
Publish-style competing responders Publishing and Consuming
At-most-once vs opt-in guaranteed routing Routing semantics

See also