Many applications must stay in sync with data provided by a service. Although Web applications typically rely on standard polling techniques to check whether data has changed, Silverlight provides several interesting options for keeping an application in sync that rely on server "push" technologies. A few years ago, I wrote several blog posts about different "push" technologies available in Silverlight that rely on sockets or HTTP Polling Duplex. Recently, I worked on a project that looked like it could benefit from pushing data from a server to one or more clients. So I thought I’d revisit the subject and provide updates to the original sample code I released.
If you’ve worked with AJAX in Web applications, you know that until browsers fully support Web sockets or other duplex (bi-directional communication) technologies it will be difficult to keep applications in sync with a server without relying on polling. The problem with polling is that you have to check for changes on the server on a timed basis, which can often be wasteful and take up unnecessary resources. With server "push" technologies, you can push data from a server to a client as it changes. Once the data is received, the client can update the user interface as appropriate. Using "push" technologies allows a client to listen for changes from the data but stay 100 percent focused on client activities, rather than worrying about polling and asking the server if anything has changed.
Silverlight provides several options for pushing data from a server to a client, including sockets, TCP bindings, and HTTP Polling Duplex. Each option has strengths and weaknesses in terms of performance and set up. But HTTP Polling Duplex is the easiest to set up and work with because it doesn’t require any settings to be changed in IIS 7 (as with TCP) and doesn’t require a specific port to be opened. In this article, I’ll demonstrate how you can use HTTP Polling Duplex in Silverlight 4 applications to push data, and how you can create a WCF server that provides an HTTP Polling Duplex binding that a Silverlight client can consume.
What Is HTTP Polling Duplex?
Technologies that allow data to be pushed from a server to a client rely on duplex functionality. Duplex (or bi-directional) communication allows data to be passed in both directions. A client can call a service and the server can call the client. HTTP Polling Duplex (as its name implies) allows a server to communicate with a client without forcing the client to constantly poll the server. This approach has the benefit of being able to run on port 80, which makes setup a breeze. Other options, such as sockets and TCP bindings, require that you use specific ports and that cross-domain policy files be exposed on port 943. If you’re looking for the best speed possible, however, sockets and TCP bindings are the way to go. But they’re not the only game in town when it comes to duplex communication.
The first time I heard about HTTP Polling Duplex (initially available in Silverlight 2) I wasn’t certain how it would be any better than standard polling used in AJAX applications. I read the Silverlight SDK, looked at various resources, and found the following definition unhelpful for understanding the benefits that HTTP Polling Duplex provided:
"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."
Although this definition explained the overall process, it sounded as if standard polling was used. Fortunately, Microsoft’s Scott Guthrie (http://Weblogs.asp.net/scottgu/) gave me a clearer definition several years ago that explains the benefits provided by HTTP Polling Duplex quite well (used with his permission):
"The \\[HTTP Polling Duplex\\] 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; then the request is "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're avoiding hitting the server repeatedly—but still get an immediate response when there is data to send."
After hearing Scott’s definition a light bulb went on and it all made sense. A client makes a request to a server to check for changes, but rather than the request returning immediately, it parks itself on the server and waits for data. It’s like waiting to pick up a pizza at the store. Instead of calling the store repeatedly to check the status, you sit in the store and wait until the pizza (the request data) is ready. Once it’s ready, you take it back home (to the client). This technique provides a lot of efficiency gains over standard polling techniques, even though it does use some polling of its own as a request is initially made from a client to a server.
So how do you implement HTTP Polling Duplex in your Silverlight applications? Let’s look at the process by starting with the server.
Creating an HTTP Polling Duplex WCF Service
Creating a WCF service that exposes an HTTP Polling Duplex binding is straightforward as far as coding goes. Add some one-way operations into an interface, create a client callback interface, and you’re ready to go. The most challenging part comes into play when configuring the service to properly support the necessary binding, and that’s more of a cut-and-paste operation once you know the configuration code to use.
To create an HTTP Polling Duplex service, you’ll need to expose server-side and client-side interfaces and reference the System.ServiceModel.PollingDuplex assembly (located at C:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Libraries\Server on my machine) in the server project. For the demo application I upgraded a basketball simulation service to support the latest polling duplex assemblies. The service simulates a simple basketball game using a Game class and pushes information about the game such as score, fouls, shots, and more to the client as the game changes over time.
Before jumping too far into the game push service, I need to discuss two interfaces used by the service to communicate in a bi-directional manner. The first, IgameStreamService, defines the methods/operations that a client can call on a server (Figure 1). The second, IgameStreamClient, defines the callback methods that a server can use to communicate with a client (Figure 2).
The IGameStreamService interface is decorated with the standard ServiceContract attribute, but it also contains a value for the CallbackContract property. This property is used to define the interface that the client will expose (IgameStreamClient, in this example) and use to receive data pushed from the service. Notice that each OperationContract attribute in both interfaces sets the IsOneWay property to true. This means that the operation can be called and passed data as appropriate; however, no data will be passed back. Instead, data will be pushed back to the client as it’s available. By looking through the IGameStreamService interface you can see that the client can request team data, whereas the IGameStreamClient interface allows team and game data to be received by the client.
One interesting point about the IGameStreamClient interface is the inclusion of the AsyncPattern property on the BeginReceiveGameData operation. Initially, I created this operation as a standard one-way operation—and it worked most of the time. However, as I disconnected clients and reconnected new ones, game data wasn’t being passed properly. After researching the problem, I realized that because the service could take up to seven seconds to return game data, things were getting hung up. By setting the AsyncPattern property to true on the BeginReceivedGameData operation and providing a corresponding EndReceiveGameData operation I was able to get around this problem and get everything to run properly. I’ll provide more details on the implementation of these two methods later in this article.
The Game Service Class
Once the interfaces were created I moved on to the game service class. The first order of business was to create a class that implemented the IGameStreamService interface. Because the service can be used by multiple clients wanting game data, I added the ServiceBehavior attribute to the class definition so that I could set its InstanceContextMode to InstanceContextMode.Single (in effect, creating a Singleton service object). Figure 3 shows the game service class, as well as its fields and constructor.
By implementing the IGameStreamService interface, GameStreamService must supply a GetTeamData() method, which is responsible for supplying information about the teams that are playing as well as individual players. GetTeamData() also acts as a client subscription method that tracks clients wanting to receive game data. Figure 4 shows the GetTeamData() method.
The key to the line of code in the GetTeamData() method is the call to GetCallbackChannel<IGameStreamClient>(). This method is responsible for accessing the calling client’s callback channel. The callback channel is defined by the IGameStreamClient interface shown in Figure 2 and used by the server to communicate with the client. Before passing team data back to the client, GetTeamData() grabs the client’s session ID and checks if it already exists in the _ClientCallbacks dictionary object used to track clients wanting callbacks from the server. If the client doesn’t exist, it adds it into the collection, then pushes team data from the Game class back to the client by calling ReceiveTeamData(). Because the service simulates a basketball game, a timer is started, if it’s not already enabled, which is then used to send data to the client randomly.
When the timer fires, game data is pushed down to the client. Figure 5 shows the _Timer_Elapsed() method that is called when the timer fires, as well as the SendGameData() method used to send data to the client.
When _Timer_Elapsed() fires, the SendGameData() method is called, which iterates through the clients wanting to be notified of changes. As each client is identified, their respective BeginReceiveGameData() method is called, which ultimately pushes game data down to the client. Recall that this method was defined in the client callback interface named IgameStreamClient and shown earlier in Figure 2. Note that BeginReceiveGameData() accepts _ReceiveGameDataCompleted as its second parameter (an AsyncCallback delegate defined in the service class) and passes the client callback as the third parameter.
The initial version of the sample application had a standard ReceiveGameData() method in the client callback interface. However, sometimes the client callbacks would work properly and sometimes they wouldn’t, which was a little baffling at first glance. After investigation I realized that I needed to implement an asynchronous pattern for client callbacks to work properly because three- to seven-second delays were occurring as a result of the timer. Once I added the BeginReceiveGameData() and ReceiveGameDataCompleted() methods everything worked properly, because each call was handled in an asynchronous manner.
The final task that had to be completed for the server to work properly with HTTP Polling Duplex was adding configuration code into Web.config. In the interest of brevity, I won’t list all the code here because the sample application includes everything you need. However, Figure 6 shows the key configuration code that handles creating a custom binding named pollingDuplexBinding and associating it with the service’s endpoint.
Calling the Service and Receiving "Pushed" Data
In Silverlight, calling the service and handling data that is pushed from the server is a simple and straightforward process. Because the service is configured with a MEX endpoint and it exposes a WSDL file, you can right-click on the Silverlight project and select the standard Add Service Reference item. After the Web service proxy is created you might notice that the ServiceReferences.ClientConfig file contains only an empty configuration element instead of the normal configuration elements created when creating a standard WCF proxy. You certainly can update the file if you want to read from it at runtime, but for the sample application I fed the service URI directly to the service proxy, as shown here:
var address = new EndpointAddress("http://localhost.:5661/GameStreamService.svc");
var binding = new PollingDuplexHttpBinding();
_Proxy = new GameStreamServiceClient(binding, address);
_Proxy.ReceiveTeamDataReceived += _Proxy_ReceiveTeamDataReceived;
_Proxy.ReceiveGameDataReceived += _Proxy_ReceiveGameDataReceived;
This code creates the proxy and passes the endpoint address and binding to use to its constructor. It then wires the different receive events to callback methods and calls GetTeamDataAsync(). Calling GetTeamDataAsync() causes the server to store the client in the server-side dictionary collection mentioned earlier so that it can receive data that is pushed. As the server-side timer fires and game data is pushed to the client, the user interface is updated, as Figure 7 shows. Figure 8 shows the _Proxy_ReceiveGameDataReceived() method responsible for handling the data and calling UpdateGameData() to process it.
In a real-life application I’d use a ViewModel class to retrieve team data, set up data bindings, and handle data that is pushed from the server. However, for the sample application I wanted to focus on HTTP Polling Duplex and keep things as simple as possible.
Pushing Data Nicely
As noted, Silverlight supports three options when duplex communication is required in an application: TCP bindings, sockets, and HTTP Polling Duplex. In this article, you’ve seen how HTTP Polling Duplex interfaces can be created and implemented on the server, as well as how they can be consumed by a Silverlight client. HTTP Polling Duplex provides a nice way to "push" data from a server while still allowing the data to flow over port 80 or another port of your choice.