Route request to specific instance

My configuration looks like this:

Endpoint1 uses RabbitMQTransport <=> Bridge <=> Endpoints on multiple machine uses MsmqTransport.

I want to send a request to a specific machine:

public Task SendRequest(TRequest request)
{
IBaseMessage msg = (IBaseMessage)request;
var sendOptions = new SendOptions();
sendOptions.RouteToSpecificInstance($“{msg.MachineHost}”);
return endpointInstance.Request(request, sendOptions);
}

Reuquest raises an exception

System.Exception: „Routing to a specific instance is only allowed if route is defined for a logical endpoint, not for an address or instance.”

Strange, becouse endpoin is configured:

var routingSettings = transport.Routing();
var bridge = routingSettings.ConnectToBridge(“Transport.RabbitMq”);
bridge.RouteToEndpoint(typeof(GetProgramPlacements), “MachineCommanderService”);

I have no idea how to solve it.

Hi Paweł

Unfortunately the Bridge does not recognise the SendOptions routing extension methods due to limitations of NServiceBus public API surface. If I understand correctly you want to send from the RabbitMQ side a message to a specific MSMQ endpoint instance. In order to do so you need to pass the destination machine name in a custom header (let’s call it DestinationMachine):

var ops = new SendOptions();
ops.SetHeader("DestinationMachine", "TheMachine");
instance.Send(new MyRequest(), ops);

Then you need to teach the bridge to understand that header. The latest version of the bridge exposes the InterceptForwarding method that allows to inject code either before or after the bridge forwarding logic. Here’s a sample:

bridgeConfig.InterceptForwarding((queue, message, dispatch, forward) =>
{
    if (message.Headers.TryGetValue("DestinationMachine", out var machine))
    {
        return forward((messages, transaction, context) =>
        {
            var newMessages = new TransportOperations(messages.UnicastTransportOperations.Select(
                o =>
                {
                    //Take only the queue name if there is also a machine name after @
                    var destinationParts = o.Destination.Split(new [] {'@'}, StringSplitOptions.RemoveEmptyEntries);

                    //Replace _ with @ for MSMQ
                    var newDestination = $"{destinationParts[0]}_{machine}";
                    return new TransportOperation(o.Message, new UnicastAddressTag(newDestination), o.RequiredDispatchConsistency, o.DeliveryConstraints);
                }).ToArray());

            return dispatch(newMessages, transaction, context);
        });

    }
    return forward(dispatch);
});

If the incoming message does not have the DestinationMachine header, it just returns forward(dispatch) which is the default bridge action. Otherwise it stores the machine in a local variable and returns a new dispatch method that, prior to dispatching the messages, replaces their machine part of the address with the value from the custom header. In this piece of code I used _ instead of @ to build the newDestination because I was running that test on a single machine. In your version you’ll use @.

Since the Bridge is going to be replaced by Router I raised an issue in the Router repo to support the SendOptions routing method out of the box in future editions.

Szymon

Paweł,

Could you describe your context some more, like why you have both RabbitMQ and MSMQ in use?

Thanks,
Udi

Hi,
I implement control of machines and logging events from them. Machines work in an isolated network.
Machine control is invoked from the MES (Manufacturing Execution Systems) server . Between the MES server and the machines is the IIS server with RabbitMq, which connects both subnets.
The MES server communicates with IIS using WCF.
The problem is that the connection between the machines and the IIS server is unstable. All machine events must be saved locally if the machine is offline.

Maybe there is a better solution, but I’m just starting my adventure with NServiceBus.

@Pawel_K did that trick work for you?

Works perfectly, thank you very much