Introduction
This sample demonstrates the use of the Routing Slip pattern with the MessageRouting project.
A routing slip allows a message to carry a list of destinations it should pass through. Each endpoint processes the message and then forwards it to the next stop on the slip. This enables dynamic workflows without hardcoding routes between endpoints.
Solution Overview
The solution consists of six projects:
- Messages – Shared message definitions.
- Sender – Initiates the message send and defines the route.
- StepA, StepB, StepC – Processing endpoints that demonstrate how a message flows between steps.
- ResultHost – The final destination that logs all endpoints the message passed through.
Enabling Routing Slips
All endpoints have the routing slip feature enabled:
endpointConfiguration.EnableFeature<RoutingSlips>();
Multiple Message Interpretations
Each step in the route defines its own interpretation of the message.
For example, StepA treats the message contract as follows:
public class SequentialProcess :
ICommand
{
public string StepAInfo { get; set; }
}
Both the Sender and ResultHost projects use the full message context by referencing the Messages project:
public class SequentialProcess :
ICommand
{
public string StepAInfo { get; set; }
public string StepBInfo { get; set; }
public string StepCInfo { get; set; }
}
When sending, all shared properties are set:
var sequentialProcess = new SequentialProcess
{
StepAInfo = "Foo",
StepBInfo = "Bar",
StepCInfo = "Baz",
};
However, in each step project, handlers only work with their own specific interpretation of the message:
public class Handler :
IHandleMessages<SequentialProcess>
{
static ILog log = LogManager.GetLogger(typeof(Handler));
public Task Handle(SequentialProcess message, IMessageHandlerContext context)
{
var routingSlip = context.Extensions.Get<RoutingSlip>();
log.Info(message.StepAInfo);
routingSlip.Attachments["Foo"] = "Bar";
return Task.CompletedTask;
}
}
Message Sending
The Sender project alternates between two send actions:
var toggle = false;
while (true)
{
var key = Console.ReadKey();
if (key.Key != ConsoleKey.Enter)
{
break;
}
if (toggle)
{
await SendToABC(endpoint);
}
else
{
await SendToAC(endpoint);
}
toggle = !toggle;
}
Route to A, C, and ResultHost
static Task SendToAC(IEndpointInstance endpoint)
{
var sequentialProcess = new SequentialProcess
{
StepAInfo = "Foo",
StepCInfo = "Baz",
};
log.Info("Sending message for step A, C");
return endpoint.Route(sequentialProcess, Guid.NewGuid(),
"Samples.RoutingSlips.StepA",
"Samples.RoutingSlips.StepC",
"Samples.RoutingSlips.ResultHost");
}
Route to A, B, C, and ResultHost
static Task SendToABC(IEndpointInstance endpoint)
{
var sequentialProcess = new SequentialProcess
{
StepAInfo = "Foo",
StepBInfo = "Bar",
StepCInfo = "Baz",
};
log.Info("Sending message for step A, B, C");
return endpoint.Route(sequentialProcess, Guid.NewGuid(),
"Samples.RoutingSlips.StepA",
"Samples.RoutingSlips.StepB",
"Samples.RoutingSlips.StepC",
"Samples.RoutingSlips.ResultHost");
}
Runtime Behavior
When routing to A, C, and ResultHost
- StepA receives the message
- StepC receives the message
- ResultHost receives the message
When routing to A, B, C, and ResultHost
- StepA receives the message
- StepB receives the message
- StepC receives the message
- ResultHost receives the message
Attachments
StepA sets a routing slip attachment:
var routingSlip = context.Extensions.Get<RoutingSlip>();
log.Info(message.StepAInfo);
routingSlip.Attachments["Foo"] = "Bar";
StepC then retrieves the attachment:
var routingSlip = context.Extensions.Get<RoutingSlip>();
log.Info(message.StepCInfo);
if (routingSlip.Attachments.TryGetValue("Foo", out var fooValue))
{
log.Info($"Found Foo value of {fooValue}");
}