Pushing Data to a Silverlight Client with a WCF Duplex Service - Part I

Silverlight provides several different ways to access data stored in remote locations.  Data can be pulled from Web Services and RESTful services and even pushed from servers down to clients using sockets (see my previous articles on sockets here, here and here).  Silverlight 2 Beta 2 introduces another way to push data from a server to a client using Windows Communication Foundation (WCF) and HTTP.  WCF's support for duplex service contracts makes this possible and opens up unique opportunities for pumping data to Silverlight clients.  In this first part of a two part series I'll demonstrate how a WCF push service can be created and cover the steps to get a sample service up and running.  The second article will focus on the client and show how to communicate with a WCF duplex service and listen for data that's sent.

Many of the WCF services out there follow the simple request-response mechanism to exchange data which works well for many applications.  However, in addition to standard HTTP bindings, WCF also supports several others including a polling duplex binding made specifically for Silverlight which allows a service to push data down to a client as the data changes.  This type of binding isn't as "pure" as the push model available with sockets since the Silverlight client does poll the server to check for any queued messages, but it provides an efficient way to push data to a client without being restricted to a specific port range.  Once a communication channel is opened messages can be sent in either direction.  The Silverlight SDK states the following about how communication works between a Silverlight client and a duplex service: 

"The Silverlight client periodically polls the service on the network layer, and checks for any new messages that the service wants to send on the callback channel. The service queues all messages sent on the client callback channel and delivers them to the client when the client polls the service."

Creating Contracts

When creating a WCF duplex service for Silverlight, the server creates a standard interface with operations.  However, because the server must communicate with the client it also defines a client callback interface.  An example of defining a server interface named IGameStreamService that includes a single service operation is shown next:

[ServiceContract(Namespace = "Silverlight", CallbackContract = typeof(IGameStreamClient))]
public interface IGameStreamService
{
    [OperationContract(IsOneWay = true)]
    void GetGameData(Message receivedMessage);
}


This interface is a little different from the standard WCF interfaces you may have seen or created.  First, it includes a CallbackContract property that points to the client interface.  Second, the GetGameData() operation is defined as a one way operation.  Client calls are not immediately returned as a result of setting IsOneWay to true and are pushed to the client instead.  The IGameStreamClient interface assigned to the CallbackContract is shown next.  It allows a message to be sent back to the client by calling the ReceiveGameData() method.

[ServiceContract]
public interface IGameStreamClient
{
    [OperationContract(IsOneWay = true)]
    void ReceiveGameData(Message returnMessage);
}

Creating the Service

Once the server and client contracts are defined a service class can be created that implements the IGameStreamService interface.  The following code creates a service that simulates a basketball game (similar to the one I demonstrated for using Sockets with Silverlight) and sends game updates to a Silverlight client on a timed basis. 

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;

namespace WCFPushService
{
    public class GameStreamService : IGameStreamService
    {
        IGameStreamClient _Client;
        Game _Game = null;
        Timer _Timer = null;
        Random _Random = new Random();

        public GameStreamService()
        {
            _Game = new Game();
        }

        public void GetGameData(Message receivedMessage)
        {

            //Get client callback channel
            _Client = OperationContext.Current.GetCallbackChannel<IGameStreamClient>();

            SendData(_Game.GetTeamData());
            //Start timer which when fired sends updated score information to client
            _Timer = new Timer(new TimerCallback(_Timer_Elapsed), null, 5000, Timeout.Infinite);
        }

        private void _Timer_Elapsed(object data)
        {
            SendData(_Game.GetScoreData());
            int interval = _Random.Next(3000, 7000);
            _Timer.Change(interval, Timeout.Infinite);
        }

        private void SendData(object data)
        {
            Message gameDataMsg = Message.CreateMessage(
                MessageVersion.Soap11,
                "Silverlight/IGameStreamService/ReceiveGameData", data);

            //Send data to the client
            _Client.ReceiveGameData(gameDataMsg);
        }
    }
}


The service first creates an instance of a Game class in the constructor which handles simulating a basketball game and creating new data that can be sent to the client.  Once the client calls the service's GetGameData() operation (a one-way operation), access to the client's callback interface is retrieved by going through the OperationContext object and calling the GetCallbackChannel() method.  The teams involved in the game are then created on the server and pushed to the client by calling the SendData() method.  This method calls the Game object's GetTeamData() method.  Although not shown here (but included in the sample code), the GetTeamData() method generates an XML message and returns it as a string.  The SendData() method then creates a WCF Message object, defines that SOAP 1.1 will be used (required for this type of communication) and defines the proper action to be used to send the XML data to the client.  The client's ReceiveGameData() operation is then called and the message is ultimately sent to the client. 

Once the client receives the team data the server will start sending simulated score data on a random basis.  When the Timer object created in the initial call to GetGameData() fires the _Timer_Elapsed() method is called which gets updated score information and pushes it to the Silverlight client by calling the SendData() method.

Creating the Service Factory

Once the service class is created a service factory can be created along with a service host.  The factory is responsible for creating the appropriate host while the host defines the service endpoint.  An example of creating service factory and host classes is shown next:

using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;

namespace WCFPushService
{
    public class PollingDuplexServiceHostFactory : ServiceHostFactoryBase
    {
        public override ServiceHostBase CreateServiceHost(string constructorString,
            Uri[] baseAddresses)
        {
            return new PollingDuplexServiceHost(baseAddresses);
        }
    }

    class PollingDuplexServiceHost : ServiceHost
    {
        public PollingDuplexServiceHost(params System.Uri[] addresses)
        {
            base.InitializeDescription(typeof(GameStreamService), new UriSchemeKeyedCollection(addresses));
        }

        protected override void InitializeRuntime()
        {
            // Define the binding and set time-outs
            PollingDuplexBindingElement bindingElement = new PollingDuplexBindingElement()
            {
                PollTimeout = TimeSpan.FromSeconds(10),
                InactivityTimeout = TimeSpan.FromMinutes(1)
            };

            // Add an endpoint for the given service contract
            this.AddServiceEndpoint(
                typeof(IGameStreamService),
                new CustomBinding(
                    bindingElement,
                    new TextMessageEncodingBindingElement(
                        MessageVersion.Soap11,
                        System.Text.Encoding.UTF8),
                    new HttpTransportBindingElement()),
                    "");

            base.InitializeRuntime();
        }
    }
}


This code was pulled directly from the Silverlight SDK example which provides a great starting point for creating WCF/Silverlight polling duplex services.  The service factory class (PollingDuplexServiceHostFactory) creates a new instance of the service host class (PollingDuplexServiceHost) within the CreateServiceHost() method.  The service host class then overrides the InitializeRuntime() method and creates a PollingDuplexBindingElement instance which defines the client's polling and inactivity timeouts.  The Silverlight SDK states the following about the PollingDuplexBindingElement class's PollTimeout and InactivityTimeout properties:

"The PollTimeout property determines the length of time (in milliseconds) that the service holds a poll from the client before returning. The InactivityTimeout property determines the length of time (in milliseconds) that can elapse without any message exchange with the client before the service closes its session."

The PollingDuplexBindingElement class is located in an assembly named System.ServiceModel.PollingDuplex.dll which is part of the Silverlight SDK.  You'll need to reference the assembly in your WCF project as well as the System.ServiceModel.Channels namespace to use the PollingDuplexBindingElement class.  Once the binding element is created a call is made to the host object's AddServiceEndPoint() method which references the PollingDuplexBindingElement object and the server's IGameStreamService interface to create a custom binding that uses HTTP under the covers for message exchange.

Once the factory and service classes are created the factory can be referenced in the service's .svc file in the following manner:

<%@ ServiceHost Language="C#" Factory="WCFPushService.PollingDuplexServiceHostFactory" %>


Looking through all of the code you can see that there's definitely some initial setup work required to get a Silverlight callable WCF duplex service created.  Since the client does have to poll the service to check for queued messages you may wonder what the benefit is over writing a manual polling Silverlight client that calls a WCF service.  Microsoft's Scott Guthrie was kind enough to provide additional details on that subject.  Here's what he had to say:

"The duplex support does use polling in the background to implement notifications – although the way it does it is different than manual polling. It initiates a network request, and then the request is effectively “put to sleep” waiting for the server to respond (it doesn’t come back immediately). The server then keeps the connection open but not active until it has something to send back (or the connection times out after 90 seconds – at which point the duplex client will connect again and wait). This way you are avoiding hitting the server repeatedly – but still get an immediate response when there is data to send."

When the client polls in the background it sends the following message to the server:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <wsmc:MakeConnection xmlns:wsmc="http://docs.oasis-open.org/ws-rx/wsmc/200702">
            <wsmc:Address>
                http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=7f64eefe-9328-4168-8175-1d4b82bef9c3
            </wsmc:Address>
        </wsmc:MakeConnection>
    </s:Body>
</s:Envelope>


In the next article I'll demonstrate how to call a WCF polling duplex service and listen for data in a Silverlight 2 application.  An example of the Silverlight interface that will be discussed is shown next:

In the meantime, you can download the Silverlight 2 Beta 2 sample application including the WCF service and Silverlight client here.




Twitter
Interested in getting live updates about blog posts and other information?  Subscribe to my Twitter feed at http://www.twitter.com/DanWahlin.

comments powered by Disqus

3 Comments

  • Just for the record...this type of HTTP push technique is often referred to as Comet. The real question is, how scalable is the server going to be? From everything I've been able to determine, it's pretty much impossible to build a scalable Comet server for IIS. This is due to the way connections and threads are handled. I've tried to get some information from Microsoft about this, but they don't have answers yet. Only time/experimentation will tell if the server is scalable enough to build a multi-player game or chat application. I'm hopeful, because the amount of work it takes to create a truly scalable socket server is tremendous.

  • Rob,
    It's difficult to say how scalable this approach is since it's brand new and the server-side .dll is still in evaluation mode (the polling duplex feature doesn't have a go-live license at this point). &nbsp;As you mention, time will tell.

  • Good Stuff, Dan. I can feel a live Stock Charting SL app coming on...

Comments have been disabled for this content.