Direct Messages and Auto-Binding Guide¶
This page explains how to use direct messages (RabbitMQ direct exchanges) and the new auto-binding behavior added to consumer definitions.
What problem this solves¶
Direct exchanges require routing keys. Manually configuring:
PublishDirect<T>()in every service that publishes/consumes, and- endpoint bindings (
Bind<T>(routingKey))
is easy to forget and easy to misconfigure.
Message attributes¶
[Direct]¶
Marks a message as being published to a direct exchange.
| C# | |
|---|---|
[RoutingKey] (optional)¶
Marks a single property/field used to compute the routing key automatically when publishing.
- 0 members marked: OK (routing key may be provided manually at publish-time).
- 1 member marked: OK.
- 2+ members marked: ❌ configuration should fail (ambiguous).
| C# | |
|---|---|
Publishing direct messages¶
Direct message publishing is configured using the existing extensions:
| C# | |
|---|---|
Case A: message has [RoutingKey]¶
If the message has exactly one [RoutingKey] member, the module auto-configures:
PublishDirect<T>()and- the routing key formatter using that member
So publishing is straightforward:
| C# | |
|---|---|
Case B: message has NO [RoutingKey] (manual routing key)¶
If the message is [Direct] but has no [RoutingKey], the module auto-configures only:
PublishDirect<T>()
You must provide a routing key manually when needed:
| C# | |
|---|---|
Important
If you publish to a direct exchange without a routing key (or with the wrong key), consumers bound to different routing keys will never receive the message.
Consuming direct messages¶
The framework exposes a simple binding API for any consumer whose definition implements
AutoBindConsumerDefinition (or any of its derivatives)
| C# | |
|---|---|
The binding still uses the existing endpoint extensions:
endpointConfigurator.Bind<T>()endpointConfigurator.Bind<T>(routingKey)
Example: many messages, same routing key¶
Example: routing key computed dynamically (generic consumer)¶
How auto-binding behaves¶
When a consumer consumes at least one [Direct]:
- Consume topology is disabled (
ConfigureConsumeTopology = false) only if the auto-binding layer can bind something. - Non-direct consumed messages are bound using
Bind<T>(). - Direct messages are bound using:
Bind<T>(routingKey)when routing keys are configured in the map
Backward compatibility¶
Existing consumers that already manually bind exchanges keep working:
- If you don’t override
ConfigureDirectRoutingKeys, auto-binding does nothing. - Manual
endpointConfigurator.Bind<...>()calls remain valid.
This allows incremental migration: update consumers only when it adds value.
Recommended conventions¶
- Prefer
[RoutingKey]on direct messages that always route by a well-known key. - Use manual routing keys only for “advanced” scenarios.
- For consumers of
[RoutingKey]direct messages, always provide bindings viaConfigureDirectRoutingKeys. - Keep routing keys stable. Treat them like part of the contract.
Troubleshooting¶
Message published but never consumed¶
Common causes:
- consumer did not bind the routing key (
Bind<T>(routingKey)missing) - routing key is different from what consumer bound to
- message has no
[RoutingKey]and publish code forgot to set the routing key manually
Startup fails due to [RoutingKey]¶
Check:
- message has exactly one
[RoutingKey]member - you didn’t annotate both a property and a field