Exposing Endpoints via WCF

Project Hosting
NuGet Package NServiceBus.Wcf (1.x)
Target NServiceBus Version: 6.x

It is possible to "expose" the message send+receive action as a WCF service. This, in effect, allows a WCF service call to be "proxied" through to a message being sent, and then waiting for the response to return the WCF result.

When doing a blocking send+receive inside a WCF service, the service implementation is a client of the Callback Functionality.

Prerequisites for WCF functionality

The WCF functionality is part of the NServiceBus.Wcf NuGet package. The package has a dependency to NServiceBus.Callback. The endpoint hosting the WCF services needs to configure the callbacks accordingly.

Expose a WCF service

To expose the endpoint as a WCF service, inherit from NServiceBus.WcfService<TRequest, TResponse>, as shown below. TRequest is the message type of the request. TResponse represents the result of processing the command and can be any type that is supported by the NServiceBus.Callback package.

Example:

public class CancelOrderService :
    WcfService<CancelOrder, ErrorCodes>
{
}

public class CancelOrderHandler :
    IHandleMessages<CancelOrder>
{
    public Task Handle(CancelOrder message, IMessageHandlerContext context)
    {
        // code to handle the message

        // return a status so that the WCF service has a return value
        return context.Reply(ErrorCodes.Success);
    }
}

public enum ErrorCodes
{
    Success,
    Fail
}

public class CancelOrder :
    ICommand
{
    public int OrderId { get; set; }
}

Configure binding and address of WCF service

To expose the WCF service, change the configuration as shown below:

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="Default">
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <services>
    <service name="Server.WebServices.CancelOrderService"
             behaviorConfiguration="Default">
      <endpoint address="mex"
                binding="mexHttpBinding" 
                contract="IMetadataExchange" />
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:9009/services/cancelOrder" />
        </baseAddresses>
      </host>
    </service>
  </services>
</system.serviceModel>

The service name in <service name="XXX" needs to match the Type.FullName that derives from NServiceBus.WcfService<TRequest, TResponse>.

OverrideBinding

It is also possible to configure the binding and address in code for each service type individually:

var wcfSettings = endpointConfiguration.Wcf();
wcfSettings.Binding(
    provider: serviceType =>
    {
        if (serviceType == typeof(MyService))
        {
            var binding = new NetNamedPipeBinding();
            var address = new Uri("net.pipe://localhost/MyService");

            return new BindingConfiguration(binding, address);
        }

        if (serviceType == typeof(CancelOrderService))
        {
            var binding = new BasicHttpBinding();
            var address = new Uri("http://localhost:9009/services/cancelOrder");

            return new BindingConfiguration(binding, address);
        }

        return new BindingConfiguration(new BasicHttpBinding());
    });

The delegate is invoked for each service type discovered. The delegate needs to return a binding configuration which contains the binding instance to be used as well as optionally an absolute listening address.

Int

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

The receiving endpoint requires a reference to NServiceBus.Callbacks.

Expose service

class MyService :
    WcfService<Message, int>
{
}

Response

public class Handler :
    IHandleMessages<Message>
{
    public Task Handle(Message message, IMessageHandlerContext context)
    {
        return context.Reply(10);
    }
}

Enum

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

The receiving endpoint requires a reference to NServiceBus.Callbacks.

Expose service

class MyService :
    WcfService<Message, Status>
{
}

Response

public class Handler :
    IHandleMessages<Message>
{
    public Task Handle(Message message, IMessageHandlerContext context)
    {
        return context.Reply(Status.OK);
    }
}

Object

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

The receiving endpoint does not require a reference to NServiceBus.Callbacks.

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; }
}

Expose service

class MyService :
    WcfService<Message, ResponseMessage>
{
}

Response

public class Handler :
    IHandleMessages<Message>
{
    public Task Handle(Message message, IMessageHandlerContext context)
    {
        var responseMessage = new ResponseMessage
        {
            Property = "PropertyValue"
        };
        return context.Reply(responseMessage);
    }
}

Cancellation

By default a request is canceled after 60 seconds. It is possible to override the cancellation behavior with:

var wcfSettings = endpointConfiguration.Wcf();
wcfSettings.CancelAfter(
    provider: serviceType =>
    {
        return serviceType == typeof(MyService) ?
            TimeSpan.FromSeconds(5) :
            TimeSpan.FromSeconds(60);
    });

The delegate is invoked for each service type discovered. The delegate needs to return a time span indicating how long a request can take until it gets canceled.

Routing

By default a request is routed to the local endpoint instance. It is possible to override the routing behavior with:

var wcfSettings = endpointConfiguration.Wcf();
wcfSettings.RouteWith(
    provider: serviceType =>
    {
        if (serviceType == typeof(MyService))
        {
            // route to fix remote destination
            return () =>
            {
                var sendOptions = new SendOptions();
                sendOptions.SetDestination("SomeDestination");
                return sendOptions;
            };
        }

        // route to this instance
        return () =>
        {
            var sendOptions = new SendOptions();
            sendOptions.RouteToThisInstance();
            return sendOptions;
        };
    });

the replying endpoint handler:

public class Handler :
    IHandleMessages<Request>
{
    public Task Handle(Request message, IMessageHandlerContext context)
    {
        // Requires NServiceBus.Callbacks
        return context.Reply(new Response());
    }
}

The delegate is invoked for each service type discovered. The delegate needs to return a function delegate which creates a SendOption instance every time it is called.

Queries and other return values

To allow clients to perform queries, it is best not to use NServiceBus. Messaging is designed for non-blocking operations, and queries are operations for which the user wishes to wait.

When performing operations that aren't straightforward as a simple query to return a value, for example a long calculation, consider invoking the operation locally where possible by referencing the DLL on the client.

Calling Web/WCF services

When invoke a Web/WCF service as a part of message handling logic, where that logic also updates transactional resources like a database, the best practice is to split it into two endpoints.

If no response is required from the Web/WCF service then instead use publish-subscribe. Have the first endpoint publish an event, to which the second endpoint subscribes, and have the second endpoint call the Web/WCF service.

If a response is required from the Web/WCF service, turn the first endpoint into a saga that sends (not publishes) a message to the second endpoint, which calls the Web/WCF service and replies with a response that is handled by the saga in the first endpoint.

Samples

Related Articles

  • Client side Callbacks
    The client (or sending process) has its own queue. When messages arrive in the queue, they are handled by a message handler.

Last modified