Friday, 24 September 2010

WCF Self Hosted Duplex Service for Chubby Client

barcodescanner[Concrete/Interesting] I received an email from a friend of mine. He was trying to build a Chubby Client, following some of the ideas I put down in my previous article. What was needed in this case was a Duplex Service that send data back from a thread outside of the main thread. My friend sent me his code and declared it couldn’t be done.

That sounded like a ‘dare’ to me, so I thought I’d have a go.

In this article I will take you through some of the major steps and discuss the overall architecture and solution. If you want the code, I've published this to Google Code so feel free to download and take a look: http://deviceservices.googlecode.com

The Goal

The main goal was to develop a device service that can be called from a Silverlight in or out of browser client. The device service would have initialization and action methods similar to those supported by an Windows OPOS device such as a scanner, printer, cash draw, customer display or MSR. For more details about Microsoft OPOS and POS device support, see Microsoft POS for .NET Overview 

The full implementation is a chubby client with a Silverlight browser client capable of running in or out of browser. This client would be launched from a console or service based service host. The host provides all application and devices services for the client through WCF Duplex Services. Within the host, the device services are responsible for abstracting and supporting the OPOS devices, application services provide APIs to application functions and databases:

apparchitecture

Duplex Services

The key problem we need to address is handling asynchronous events at the service. Why? Well we want to build a Silverlight client. We want this because we may want to run in a thin client configuration with the client hosted in a browser calling network or web services. For the application services, a normal one way web service or REST service would be fine. The client would simply call the service and the service would return data. But there are some application services where asynchronous events are a better way forward. Take notifications in workflow as an example. Here you don’t want to hold up the client and wait for the service to notify you of a state change. This state change could come at any time. Another example is large list data. You don’t want to call and have returned back all the data to the client and bound to some UI element. The data transfers could be huge and the client would be required to hold all this data.

Now considering the support of devices in our service, we again need to think about an asynchronous notification architecture as we request an action at the device such as ‘Scan when a barcode is available’ and a some time in the future, when a barcode is presented to the scanner, the device wants to tell us ‘here is a barcode I just scanned’. A further complication here is that the event notifications are typically not raised in the same thread as that used to handle the client request. 

In ASP.Net we would look towards AJAX to solve these problems. In the world of services, we need to implement Duplex or dual channel services. The diagram below illustrates this for a service supporting a POS scanner:opos

WCF Duplex Services allow us to define a client callback contract again the service contract. What this means is the client exchanges a service contract with the service; this allows the service to notify the client of service events.

Duplex Service Options

WCF provides a number of ways to implement duplex services against Silverlight 4 clients:

HTTP Polling Duplex Service

This is the one I’ll be looking at in more detail. It is suitable for Chubby Clients, however it doesn’t scale out client connections well and has trouble working across network boundaries. However, out of the box, this is the simplest duplex service to code and support in in a client.

Duplex Service using Sockets

You can also use private sockets to set up a duplex channel between Silverlight and the service. Very fast, scales well but only works on a limited range of ports (Silverlight sandbox restriction) and a lot of code needs to be hand forged both service and client side.

Duplex Service with Net.TCP Bindings

Ultimately, this is the best balance between non-functional like scalability, performance and network awareness. However, the code is more involved so I won’t be illustrating it here.

Proof of Concept

In the proof of concept, focus is given to the Device abstraction layer and support for events raised back to the client. These events would normally be raised outside of the main thread and the purpose of the proof of concept is to illustrate this can be done in a chubby Silverlight-WCF architecture. For this, I’m going to use a System.Threading.Timer to raise the client notifications.

To keep things simple, the implementation will also restrict itself to one client pre-service. Finally, no work will be done to provide application services or actual integration with Microsoft OPOS. I will leave that as an exercise for the reader.

DeviceService

Self Hosted Service

I’ll be honest, I like to see how things work, at least I do the first few times I do something.

Self Hosted WCF services let you get into the details as you have to write the service, not press a few buttons on a wizard.

Also, a self hosted service, all the bindings and endpoint behaviours are down to you. You have to code it rather than define it is a CONFIG file.

Lastly, we are building a console application so there is full control over the running, debugging and management of the application.

Of course, for a production system you may consider IIS hosted. This will provide the best performance and scalability and ease of deployment.

For chubby clients, self-hosted, either as console or service probably remains the choice solution as you need to package and deploy service and client together and for each service, there is likely to be only one client.

The Host

As ever, the host is simplicity itself:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Starting WCF Host for DuplexDeviceEvent...");
        string baseAddress = "http://localhost:8000";
        DeviceEventRunner runner = new DeviceEventRunner();

        using (ServiceHost host = new ServiceHost(typeof(DeviceEventService), new Uri(baseAddress)))
        {
            host.AddServiceEndpoint(typeof(IPolicyRetriever), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior());
            host.AddServiceEndpoint(typeof(IDeviceEventService), new PollingDuplexHttpBinding(), "DeviceEventService");

            ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
            host.Description.Behaviors.Add(smb);
            host.Open();

            Console.WriteLine("WCF Host Running...");
            Console.WriteLine("Press  to shutdown");
            Console.ReadLine();

            host.Close();
        }
    }
}

All that’s going on here is a service host is being initialized and run. Since we are calling this host from Silverlight we need to ensure there is a default Policy available. Beyond that, we are creating an endpoint for the service that follows the PollingDuplexHTTPBinding protocol.

ServiceMetadataBehaviour

As a side note, this little bit of code enables the service to publish itself. This is very useful as it will allow us to create a service reference in our client.

Device Runner

You will see a static object defined in the programs main method. This is the device runner that will simulate the device events. The code behind this class simply starts a System.Threading.Timer. In the timer’s callback, if a client is connected, the callback event for the service is executed:

private static void updateClient(Object stateinfo)
{
    try
    {
        if (theClient != null)
        {
            Console.WriteLine("Updating Client {0}", clientId);
            DeviceEventData eventData = new DeviceEventData();
            eventData.message = "Test Data";
            eventData.timestamp = DateTime.Now;
            theClient.SendDeviceEventData(eventData);
        }
    }
    catch (CommunicationException ex)
    {
        //client error so dispose of client
        Console.WriteLine("Client Error: {0}", ex.Message);
        Console.WriteLine("Closing connection to client {0}", clientId);
        theClient = null;
        clientId = String.Empty;
    }
}

public static void Register(IDeviceEventServiceClient client, string id)
{
    theClient = client;
    clientId = id;
    Console.WriteLine("Client {0} registered.", id);
}

The service runner is also responsible for accepting the client registration through the Connect interface.

Notice that exceptions can be raised during the callback processing. The main exception is a failure to reach the client during callback, typically because the client has gone. This is picked up as a CommunicationException. In this implementation the service runner simply removed its reference to the client.

Service Contracts and Implementation

When we define the service contract we also name the callback contract. This is defined in the interface for the service:

[ServiceContract(Namespace = "Silverlight", CallbackContract = typeof(IDeviceEventServiceClient))]
public interface IDeviceEventService
{
    [OperationContract(IsOneWay = true)]
    void Connect(string id);
}

[ServiceContract]
public interface IDeviceEventServiceClient
{
    [OperationContract(IsOneWay = true)]
    void SendDeviceEventData(DeviceEventData data);
}

Two things happen when the client connects. First, a callback interface is created against the callback channel. This callback interface is then passed to the service runner where is is remembered and used during the service callback processing.

Callback data

In this example, the callback to the client is responsible for sending data back to the client for display. In this case, the data is a timestamp identifying when the event was fired and some test data. This is packed into a data structure called DeviceEventData

public class DeviceEventData
{
    public DateTime timestamp { get; set; }
    public string message{ get; set; }
}

...

DeviceEventData eventData = new DeviceEventData();
eventData.message = "Test Data";
eventData.timestamp = DateTime.Now;
theClient.SendDeviceEventData(eventData);

To get this data to the client, all that we need to do is create a data object, initialize it and pass the data as a parameter in the service callback interface.

Building a test client

Silverlight 4 really does make life easy, even when talking to duplex services. The key to this is to create a service reference.

The service reference takes care of all the low level stuff, including handling all asynchronous aspects of the relationship between the client and service.

Once we have a service reference, we can hook up to the service and service callback:

public partial class MainPage : UserControl
{
    ObservableCollection eventData;

    public MainPage()
    {
        InitializeComponent();

        eventData = new ObservableCollection();

        EndpointAddress address = new EndpointAddress("http://localhost:8000/DeviceEventService");

        CustomBinding binding = new CustomBinding(
            new PollingDuplexBindingElement(),
            new BinaryMessageEncodingBindingElement(),
            new HttpTransportBindingElement());

        DeviceEventService.DeviceEventServiceClient client = new DeviceEventService.DeviceEventServiceClient(binding, address);
        client.SendDeviceEventDataReceived += new EventHandler(client_SendDeviceEventReceived);
        client.ConnectAsync(Guid.NewGuid().ToString());
        EventListBox.ItemsSource = eventData;
    }

    void client_SendDeviceEventReceived(object sender, DeviceEventService.SendDeviceEventDataReceivedEventArgs e)
    {
        if (!eventData.Contains(e.data))
        {
            eventData.Add(e.data);
        }
    }
}

As you can see, the client is creating an ObservableCollection to hold the event data returned by the service callback. Next, the client connects up to the service endpoint, creates an implementation object for the service and initializes this object. Initialization means:

  • Associating the object with the endpoint binding,
  • Setting up a delegate to process the callback event and
  • Call the Connect service method. Note this call has an asynchronous implementation.

Note that the event data ObservableCollection is then bound to the clients visual component, in this case a ListBox.

Now when the callback delegate is fired, all that we need to do is add the event data to the ObservableCollection, and because this collection is observable, the ListBox is told about it.

In the XAML, the listbox binds the data to the list items:

<ListBox Grid.Row="1" x:Name="EventListBox">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Width="770">
                <Grid.RowDefinitions>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                <TextBlock Grid.Row="1" Text="{Binding timestamp}"></TextBlock>
                <TextBlock Grid.Column="1" Text="{Binding message}" FontWeight="Bold" Foreground="Red" HorizontalAlignment="Right"></TextBlock>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

and hey presto…

Wow, that was easy, Silverlight 4 seems to be doing all the work!

Conclusion

As you can see, its easy to build duplex service under WCF. What’s more, Silverlight 4 is well matched to this technology, providing simple binding against the callback event.