Client side Callbacks

Project Hosting
NuGet Package NServiceBus (3.x)
Standard support for version 3.x of NServiceBus has expired. For more information see our Support Policy.

Callbacks allow to use messaging behind a synchronous API that can't be changed. A common use case is introducing messaging to existing synchronous Web or WCF applications. The advantage of using callbacks is that they allow to gradually transition applications towards messaging.

Handling responses in the context of a message being sent

When sending a message, a callback can be registered that will be invoked when a response arrives.

If the server process returns multiple responses, NServiceBus cannot know which response message will be the last. To prevent memory leaks, the callback is invoked only for the first response. Callbacks won't survive a process restart (common scenarios are a crash or an IIS recycle) as they are held in memory, so they are less suitable for server-side development where fault-tolerance is required. In those cases, sagas are preferred.

To handle responses from the processing endpoint, the sending endpoint must have it's own queue. Therefore, the sending endpoint cannot be configured as a SendOnly endpoint. Messages arriving in this queue are handled using a message handler, similar to that of the processing endpoint, as shown:

public class MyMessageHandler :
    IHandleMessages<MyMessage>
{
    public void Handle(MyMessage message)
    {
        // do something in the client process
    }
}

Prerequisites for callback functionality

In NServiceBus Version 5 and below callbacks are built into the core NuGet.

In NServiceBus Version 6 and above callbacks are shipped as NServiceBus.Callbacks NuGet package. This package has to be referenced by the requesting endpoint.

Using Callbacks

The callback functionality can be split into three categories based on the type of information being used; integers, enums and objects. Each of these categories involves two parts; send+callback and the response.

Int

The integer response scenario allows any integer value to be returned in a strong typed manner.

This type of callback won't cause response messages to end up in the error queue if no callback is registered.

Send and Callback

var message = new Message();
bus.Send(message)
    .Register<int>(
        callback: response =>
        {
            log.Info($"Callback received with response:{response}");
        });

Response

public class Handler :
    IHandleMessages<Message>
{
    IBus bus;

    public Handler(IBus bus)
    {
        this.bus = bus;
    }

    public void Handle(Message message)
    {
        bus.Return(10);
    }
}

Enum

The enum response scenario allows any enum value to be returned in a strong typed manner.

Send and Callback

var message = new Message();
bus.Send(message)
    .Register<Status>(response =>
    {
        log.Info($"Callback received with response:{response}");
    });

Response

public class Handler :
    IHandleMessages<Message>
{
    IBus bus;

    public Handler(IBus bus)
    {
        this.bus = bus;
    }

    public void Handle(Message message)
    {
        bus.Return(Status.OK);
    }
}

Object

The Object response scenario allows an object instance to be returned.

The Response message

This feature leverages the message Reply mechanism of the bus and hence the response need to be a message.

public class ResponseMessage :
    IMessage
{
    public string Property { get; set; }
}

Send and Callback

var message = new Message();
bus.Send(message)
    .Register(
        callback: asyncResult =>
        {
            var localResult = (CompletionResult) asyncResult.AsyncState;
            var response = (ResponseMessage) localResult.Messages[0];
            log.Info($"Callback received with response:{response.Property}");
        },
        state: null);
In Version 3 if no handler exists for a received message then NServiceBus will throw an exception. As such for this scenario to operate a fake message handler is needed on the callback side.
public class ObjectResponseMessageHandler :
    IHandleMessages<ResponseMessage>
{
    public void Handle(ResponseMessage message)
    {
    }
}

Response

public class Handler :
    IHandleMessages<Message>
{
    IBus bus;

    public Handler(IBus bus)
    {
        this.bus = bus;
    }

    public void Handle(Message message)
    {
        var responseMessage = new ResponseMessage
        {
            Property = "PropertyValue"
        };
        bus.Reply(responseMessage);
    }
}

When to use callbacks

Using callbacks in IHandleMessages<T> classes can cause deadlocks and/or other unexpected behavior, so do not call the callback APIs from inside a Handle method in an IHandleMessages<T> class.
Due to the fact that callbacks won't survive restarts, use callbacks when the data returned is not business critical and data loss is acceptable. Otherwise, use request/response with a message handler for the reply messages.

When using callbacks in a ASP.NET Web/MVC/Web API, the NServiceBus callbacks can be used in combination with the async support in Asp.Net to avoid blocking the web server thread and allowing processing of other requests. When response is received, it is handled and returned to the client side. Web clients will still be blocked while waiting for response. This scenario is common when migrating from traditional blocking request/response to messaging.

Message routing

Callbacks are routed to queues based on the machine's hostname. On broker transports additional queues are created for each endpoint/hostname combination e.g. Sales.MachineA, Sales.MachineB. Callbacks do not require any additional queue configuration from the user.

When more than one instance, of a given endpoint, is deployed to the same machine, the responses might be delivered to the incorrect instance and callback never fires.

Samples


Last modified