MassTransit Autofac Request and Response

Messages send with MassTransit typically are fire and forget. This is because distributed systems operate in different processes on different machines, giving it the benefit of horizontal scaling (with trade offs, but that’s another discussion entirely). However, the need may arise to use request + response in a distributed system. So this tutorial will show how this can be achieved with MassTransit.

If you have followed some of my other tutorials, you will know that I like to separate the RabbitMQ config with a virtual host and username specific to the tutorial. My config for this tutorial is:

<add key="RabbitMQHost" value="rabbitmq://localhost/tutorial-req-resp" />
<add key="RabbitMQUsername" value="tutorial-req-resp" />
<add key="RabbitMQPassword" value="apple" />

This tutorial will simply show the main components required to set up a Request/Response with MassTransit. You can follow along with the completed project here.

*Update*: When I had drafted this, the MassTransit Documentation didn’t have an example for Request/Response. Now there is excellent documentation here. So I will reduce the scope of this tutorial to registering the IRequestClient<…> in an Ioc Framework (Autofac).

Tutorial

When using Publish + Subscribe, we typically get the IBus or IBusControl injected and use the .Publish or .Send methods to fire and forget. But for Request Response, we will use IRequestClient<…>. Open up RequestClientModule.cs. You will see:

Uri address = new Uri(ConfigurationManager.AppSettings["ServiceFullUri"]);
TimeSpan requestTimeout = TimeSpan.FromSeconds(30);

builder.Register(c => new MessageRequestClient<CheckOrderStatus, OrderStatusResult>(c.Resolve<IBus>(), address, requestTimeout))
    .As<IRequestClient<CheckOrderStatus, OrderStatusResult>>()
    .SingleInstance();

Line 4: This is where our concrete type MessageRequestClient is constructed, which is one of the many value adds for MassTransit. It handles the plumbing of creating a RequestId and correlates messages accordingly.

You might be wondering why I used MessageRequestClient? Because in the official sample, a helper extension method is used instead. If we look at the MassTransit source, we will see the extension is just that, a helper:

public static IRequestClient<TRequest, TResponse> CreateRequestClient<TRequest, TResponse>(this IBus bus, Uri address, TimeSpan timeout,
    TimeSpan? ttl = default(TimeSpan?), Action<SendContext<TRequest>> callback = null)
    where TRequest : class
    where TResponse : class
{
    return new MessageRequestClient<TRequest, TResponse>(bus, address, timeout, ttl, callback);
}

So in our RequestClientModule.cs, you could replace the builder.Register with:

builder.Register(c =>
{
    var bus = c.Resolve<IBus>();

    return bus.CreateRequestClient<CheckOrderStatus, OrderStatusResult>(address, requestTimeout);
})
    .As<IRequestClient<CheckOrderStatus, OrderStatusResult>>()
    .SingleInstance();

Then you would get the exact same behavior. Choose whatever method you prefer.

Now we take a quick peek in the Consumer. Open up CheckOrderStatusConsumer.cs and you will see:

public async Task Consume(ConsumeContext<CheckOrderStatus> context)
{
    Console.Out.WriteLine("Received OrderId: " + context.Message.OrderId);
    context.Respond(new SimpleOrderStatusResult { OrderMessage = string.Format("Echo the OrderId {0}", context.Message.OrderId) });
}

And the important line here is context.Respond(…). This is the other part of the MassTransit value add plumbing. It makes sure that it responds and retains the RequestId. You can actually look and see what the RequestId is with context.RequestId .

There you have it, pretty simple, but powerful features provided by MassTransit ontop of whatever message transport (eg. RabbitMQ, AzureSB) you choose.

Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.