It is possible to expose the message send+receive action as a WCF service. In effect, this allows a WCF service call to be "proxied" through to a message being sent, and then wait 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.
. 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.
, 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.
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
must match the Type.
that derives from NServiceBus.
.
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.
.
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.
.
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.
.
The Response message
This feature leverages the message Reply mechanism of the bus and hence the response needs 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 usually must wait.
When performing operations that aren't as 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 invoking 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 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.