Monday, November 2, 2009

Migrating Classic WCF to the Service Bus

The Service Bus (within Microsoft's new .NET Services product) is a nice bit of technology that allows two-way communication between nodes on the Internet, regardless of the presence of firewalls, NAT gateways or other complicated network topologies. It makes possible all sorts of useful tools, such as Rich's and Rob's SocketShifter.

What's really nice about Service Bus's implementation, however, (and often overlooked) is that it's nothing more than a new set of WCF bindings which result in the creation of publicly reachable and discoverable endpoints.

With that in mind, I wondered whether I could take my previous simple WCF example, and change it to use the Service Bus by doing nothing more than modifying the config. In theory, it seems that this should be possible once the Service Bus becomes a standard part of the .NET framework - but for now it proves difficult. This is because installing the .NET Services CTP will modify your machine.config file such that netTcpRelayBinding cannot be added again as an extension within an app.config, but HAS to be added in that way for machines without the CTP installed (i.e. all of your customers' servers, or indeed an Azure VM).

The only sensible way to achieve this, therefore, is by explicitly setting options within the code itself - but this proved pretty easy.


Messaging Model

Let's start by modifying the code of the previous example slightly so that we emphasise the delivery of a message and a reply. We'll make the GetTimestamp method take a string, which gets printed at the server and copied back within the reply:

SharedTypes.IHealthCheck
...
[OperationContract]
string GetTimestamp(string serviceName);

Server.HealthCheck
...
public string GetTimestamp(string serviceName)
{
    Console.WriteLine("Request received from {0}", serviceName);
    return String.Format("{0}: {1}", serviceName, DateTime.Now.ToString());
}

Client.Program
...
while (true)
{
    Console.WriteLine(healthCheck.GetTimestamp("My service"));



Migrating to Service Bus

In both the Server and the Client, we now need to configure the endpoint as follows (If you don't yet have a .NET Services account, you can create one at http://www.microsoft.com/windowsazure/developers/dotnetservices/):

  • Specify a Service Bus address (sb://yourAccount.servicebus.windows.net/...)
  • Specify an appropriate Service Bus binding (here, we'll use NetTcpRelayBinding)
  • Specify a new behaviour which contains your Service Bus credentials

As mentioned before, the only safe way of doing this at the moment is programatically:

Server.Program

Configure the endpoint of the ServiceHost, just before opening it:

(Assembly reference: Microsoft.ServiceBus)

using System.ServiceModel.Description;
using Microsoft.ServiceBus;
...
static void Main(string[] args)
{
    // Construct EndpointBehaviour for SB username and password
    TransportClientEndpointBehavior serviceBusCredential =
        new TransportClientEndpointBehavior();
    serviceBusCredential.CredentialType =
        TransportClientCredentialType.UserNamePassword;
    serviceBusCredential.Credentials.UserName.UserName = "yourAccount";
    serviceBusCredential.Credentials.UserName.Password = "yourPassword";
 
    ServiceHost serviceHost = new ServiceHost(typeof(HealthCheck));
 
    // Set the binding manually (overriding the app.config)
    ServiceEndpoint serviceEndpoint = serviceHost.Description.Endpoints[0];
    serviceEndpoint.Address = new EndpointAddress(
        "sb://yourAccount.servicebus.windows.net/test/");
    serviceEndpoint.Binding = new NetTcpRelayBinding();
    serviceEndpoint.Behaviors.Add(serviceBusCredential);
 
    serviceHost.Open();
 
    // Keep the process running (hence the service open) for an hour
    System.Threading.Thread.Sleep(3600000);
}


Client.HealthCheck

Configure the endpoint of the ClientBase class we're inheriting from on construction, to ensure we only do it once:

(Assembly reference: Microsoft.ServiceBus)

using Microsoft.ServiceBus;
...
public HealthCheck()
{
    // Construct EndpointBehaviour for SB username and password
    TransportClientEndpointBehavior serviceBusCredential =
        new TransportClientEndpointBehavior();
    serviceBusCredential.CredentialType =
        TransportClientCredentialType.UserNamePassword;
    serviceBusCredential.Credentials.UserName.UserName = "yourAccount";
    serviceBusCredential.Credentials.UserName.Password = "yourPassword";
 
    // Set the binding manually (overriding the app.config)
    this.Endpoint.Address = new EndpointAddress(
        "sb://yourAccount.servicebus.windows.net/test/");
    this.Endpoint.Binding = new NetTcpRelayBinding();
    this.Endpoint.Behaviors.Add(serviceBusCredential);
}


That's it! If you fire up the server and the client, you should now see them working exactly as before - except you can now have the server running on your home machine behind a NAT gateway, and the client running on your office machine behind a corporate firewall!

No comments:

Post a Comment