Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Samples

Propagating Tenant Information to Downstream Endpoints

Component: NServiceBus
NuGet Package: NServiceBus (8.x)

This sample shows how to configure the NServiceBus pipeline to propagate tenant information to downstream endpoints automatically. The sample assumes that the tenant information is passed as a message header.

Code walk-through

Attaching tenant information to the messages

In most cases the best way to attach tenant information to messages is with a custom message header. The following code demonstrates how to set a custom tenant_id header.

var options = new SendOptions();
options.SetHeader("tenant_id", tenantId);
await endpointInstance.Send(new PlaceOrder(), options);

Creating behaviors

Two behaviors are required to propagate the tenant information.

Retrieving and storing the tenant information

The first behavior is responsible for extracting the tenant information from the incoming message header and placing it in the pipeline execution context bag. This behavior executes as part of the message receive pipeline.

public class StoreTenantIdBehavior :
    Behavior<IIncomingLogicalMessageContext>
{
    public override Task Invoke(IIncomingLogicalMessageContext context, Func<Task> next)
    {
        if (context.MessageHeaders.TryGetValue("tenant_id", out var tenant))
        {
            context.Extensions.Set("TenantId", tenant);
        }
        return next();

    }
}

Propagating the tenant information to the outgoing messages

The second behavior is responsible for attaching the tenant information header(s) to outgoing messages based on the pipeline context. This behavior executes as part of the message send pipeline.

public class PropagateTenantIdBehavior :
    Behavior<IOutgoingLogicalMessageContext>
{
    public override Task Invoke(IOutgoingLogicalMessageContext context, Func<Task> next)
    {
        if (context.Extensions.TryGet("TenantId", out string tenant))
        {
            context.Headers["tenant_id"] = tenant;
        }
        return next();
    }
}

Registering the behaviors

The following code is needed to register the created behaviors in the pipeline.

var pipeline = endpointConfiguration.Pipeline;
pipeline.Register(new StoreTenantIdBehavior(), "Stores tenant ID in the session");
pipeline.Register(new PropagateTenantIdBehavior(), "Propagates tenant ID to outgoing messages");

Message handlers

The OrderPlaced message handler in the Sales endpoint publishes an OrderAccepted event.

class PlaceOrderHandler :
    IHandleMessages<PlaceOrder>
{
    static readonly ILog log = LogManager.GetLogger<PlaceOrderHandler>();

    public Task Handle(PlaceOrder message, IMessageHandlerContext context)
    {
        var tenant = context.MessageHeaders["tenant_id"];

        log.Info($"Processing PlaceOrder message for tenant {tenant}");

        return context.Publish(new OrderAccepted());
    }
}

In addition to that, both the OrderAccepted and OrderPlaced message handlers log the tenant ID header value extracted from the incoming message.

Running the sample

Run the sample. Once running, in the Client console press any key to send messages. Note that the logged tenant ID is the same in both the Sales and Billing endpoints.

Related Articles