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:
- Socket Support in Silverlight 2: Part I
- Socket Suport in Silverlight 2: Part II
- Creating a Silverlight 2 Client Access Policy Socket Server
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 article, I demonstrate how a WCF push service can be created and cover the steps to get a sample service up and running. In Part II, I 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. Here is an example of defining a server interface named IGameStreamService that includes a single service operation:
[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 lets a message 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 I GameStreamService 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.