AI agents: use the documentation index at llms.txt to locate machine-readable pages. This section is indexed by https://docs.particular.net/nservicebus/llms.txt. The markdown version of this page is served as plain text. An MCP server at /mcp serves the same content via the search_docs and read_doc tools; it is read-only and needs no credentials. Markdown versions of documentation pages are available by appending .md to the page URL. Directory URLs use index.md. They are served as text/plain because some retrieval backends reject text/markdown.

Routing system extensibility points

Component:
NServiceBus
NuGet Package:
NServiceBus 9.x

Extending the NServiceBus routing subsystem with a custom data source makes sense in the following scenarios:

  • When centralizing all routing information in a database.
  • When dynamically calculating routes using service discovery.
  • When routing by a convention based on message type names.

Applying routing strategies

Routing a message involves the following steps:

  • Select the routing strategy based on the message intent
  • Determine the properties for the selected routing strategy based on the message type, for example, the destination address for a command routed with the unicast strategy

Command routing

To extend command routing, routing extensions can access the routing table from EndpointConfiguration or from a feature:

var settings = endpointConfiguration.GetSettings();
var routingTable = settings.GetOrCreate<UnicastRoutingTable>();
routingTable.AddOrReplaceRoutes("MySource",
    new List<RouteTableEntry>
    {
        new RouteTableEntry(typeof(MyCommand),
            UnicastRoute.CreateFromEndpointName("MyEndpoint"))
    });

The routing table can be modified in the feature setup phase or can be passed further, e.g. to a FeatureStartupTask, and updated periodically when the source of the routing information changes.

protected override void Setup(FeatureConfigurationContext context)
{
    var routingTable = context.Settings.Get<UnicastRoutingTable>();
    var refresherTask = new Refresher(routingTable);
    context.RegisterStartupTask(refresherTask);
}
public Refresher(UnicastRoutingTable routingTable)
{
    this.routingTable = routingTable;
}

protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken)
{
    timer = new Timer(
        callback: _ =>
        {
            routingTable.AddOrReplaceRoutes("MySource", LoadRoutes());
        },
        state: null,
        dueTime: TimeSpan.FromSeconds(30),
        period: TimeSpan.FromSeconds(30));
    return Task.CompletedTask;
}

The sourceKey parameter is used as a unique key. When AddOrReplaceRoutes is called the first time with a given source key, the routes are added to the table. When it is called subsequently, the routes previously registered under that source key are replaced by the new routes.

The routing table API is thread-safe and atomic, meaning either all the changes from the passed collection are successfully applied or none are.

The routing system prevents route ambiguity. If new or replaced routes conflict with existing ones, AddOrReplaceRoutes throws an exception and the routing table stays unchanged. The route extension must handle the exception. Usually the safest option is to raise a critical error so the endpoint stops instead of routing messages incorrectly. By default a critical error is only logged, so the endpoint needs a critical error action that stops it.

public RobustRefresher(UnicastRoutingTable routingTable, CriticalError criticalError)
{
    this.routingTable = routingTable;
    this.criticalError = criticalError;
}

protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken)
{
    timer = new Timer(
        callback: _ =>
        {
            try
            {
                routingTable.AddOrReplaceRoutes("MySource", LoadRoutes());
            }
            catch (Exception exception)
            {
                criticalError.Raise("Ambiguous route detected", exception, cancellationToken);
            }
        },
        state: null,
        dueTime: TimeSpan.FromSeconds(30),
        period: TimeSpan.FromSeconds(30));
    return Task.CompletedTask;
}

Event routing

Event routing differs depending on the transport capabilities. Multicast transports which support the Publish-Subscribe pattern natively implement the event routing themselves. Refer to specific transport documentation for details on extensibility points.

Transports without that support rely on NServiceBus core routing for event delivery. The key concept is the publishers collection, which contains information on the logical endpoint that publishes each event. Routing extensions can access the publishers collection from EndpointConfiguration or from the Feature setup code:

protected override void Setup(FeatureConfigurationContext context)
{
    var publishers = context.Settings.Get<Publishers>();
    var publisherAddress = PublisherAddress.CreateFromEndpointName("PublisherEndpoint");
    publishers.AddOrReplacePublishers(
        sourceKey: "MySource",
        entries: new List<PublisherTableEntry>
        {
            new PublisherTableEntry(typeof(MyEvent), publisherAddress)
        });
}

The sourceKey parameter has the same meaning and effect as for the routing table.

The publishers collection is thread-safe, and all operations on that collection are atomic.

Advanced routing customizations

If there's a need to adjust the routing based on criteria other than the message type, the routing pipeline stage allows routing customization for all messages emitted by the endpoint.

Related Articles