This sample demonstrates explicit polymorphic event routing with the IBM MQ transport. An Orders endpoint publishes either an OrderPlaced or an ExpressOrderPlaced event. The Shipping endpoint has a handler for OrderPlaced and uses explicit subscription routes to receive both event types.
How it works
The IBM MQ transport uses a topic-per-event model: each concrete event type is published to its own topic. Subscribing to OrderPlaced alone would only create a subscription on the OrderPlaced topic, missing messages published as ExpressOrderPlaced.
To receive both, the subscriber explicitly maps the OrderPlaced subscription to multiple topics using SubscribeTo:
// Explicitly subscribe to both concrete types' topics when handling OrderPlaced
ibmmq.Topology.SubscribeTo<OrderPlaced, OrderPlaced>();
ibmmq.Topology.SubscribeTo<OrderPlaced, ExpressOrderPlaced>();
NServiceBus includes all types in the .NET inheritance chain in the NServiceBus. message header when publishing. The handler for OrderPlaced receives the full ExpressOrderPlaced instance because NServiceBus matches the enclosed type chain against registered handlers.
Prerequisites
The sample requires a running IBM MQ broker. A Docker Compose file is included:
docker compose up -d
This starts IBM MQ with queue manager QM1 on port 1414. The management console is available at https:/ (credentials: admin / passw0rd).
Running the sample
- Start Shipping first.
EnableInstallers()creates its queue, topics, and durable subscriptions on the broker. - Start Orders.
- Press
O- Shipping logsReceived OrderPlaced. - Press
E- Shipping logsReceived ExpressOrderPlaced, delivered via the explicit subscription on theExpressOrderPlacedtopic.
Code walk-through
Event hierarchy
public record OrderPlaced(Guid OrderId, string Product) : IEvent;
public record ExpressOrderPlaced(Guid OrderId, string Product)
: OrderPlaced(OrderId, Product);
ExpressOrderPlaced inherits from OrderPlaced using C# record inheritance.
Subscription routing
The subscriber must explicitly declare which concrete types' topics to subscribe to. Without this, the transport throws an InvalidOperationException at startup because OrderPlaced has a known descendant type (ExpressOrderPlaced).
// Explicitly subscribe to both concrete types' topics when handling OrderPlaced
ibmmq.Topology.SubscribeTo<OrderPlaced, OrderPlaced>();
ibmmq.Topology.SubscribeTo<OrderPlaced, ExpressOrderPlaced>();
Publishing
if (key.Key == ConsoleKey.E)
{
await session.Publish(new ExpressOrderPlaced(orderId, "Widget"));
Console.WriteLine($"Published ExpressOrderPlaced {orderId}");
}
else if (key.Key == ConsoleKey.O)
{
await session.Publish(new OrderPlaced(orderId, "Widget"));
Console.WriteLine($"Published OrderPlaced {orderId}");
}
Both events are published with the same session. call. Each is published to its own topic (DEV. and DEV. respectively). NServiceBus populates NServiceBus. with the full type chain automatically.
Subscriber handler
sealed class OrderPlacedHandler : IHandleMessages<OrderPlaced>
{
public Task Handle(OrderPlaced message, IMessageHandlerContext context)
{
var messageType = message.GetType().Name;
Console.WriteLine($"Received {messageType}: OrderId={message.OrderId}, Product={message.Product}");
return Task.CompletedTask;
}
}
The handler is registered for OrderPlaced only. Logging message. confirms the full derived type is preserved through delivery - the handler receives an ExpressOrderPlaced instance, not a downcast OrderPlaced.