This article is compatible with the latest version of Silverlight.
This article is Part 1 of the series The Duplex Story in Silverlight.
Silverlight offers us many choices to work with services to get data into our applications. Supported technologies include WCF, ASMX, REST, WCF RIA Services etc. Through the use of any of these, it’s quite easy to get data from the server to the client application and vice versa. They all have one thing in common: before the data is sent, the client has to perform a request to the server to do so. The communication is known to be client-initiated. But what if the server wants to initiate communication by sending some data to the client, without there being a request first? In this case, we need to use duplex communication, so that both sides of the communication channel can start sending data.
In this 3 part article, we’ll look at the options that Silverlight has on board to perform duplex communication. In this first part, we’ll start by looking at where this can be useful, followed by a first possible implementation, the HttpPollingDuplex. The source code for this article can be downloaded here.
The need for duplex communication
Before we start with the first possible implementation of duplex communication, let’s first take a look in which scenarios this can be useful.
Assume the following scenario (which will be the scenario for the demo later on as well).
You have been asked to build a Silverlight application that displays train delays as can be seen in the screenshot below. An external system drops an XML file in a specific folder whenever a train is delayed and drops another XML file when the train delay is to be removed. Your Silverlight application should be displaying up-to-date information about these train delays.
A first attempt on how to solve this could be the following. We can write a WCF service that includes a method which returns these delays (internally, it would read out the contents of the folder and return them as a list of delays). In our Silverlight application, we can code a Timer that polls the service every 30 seconds. That would mean that each 30 seconds, the server gets a request from our application.
In rush hour traffic, we can assume that every 30 seconds, the service will return some new information, meaning that our requests are useful: we get some new information on train delays with each request. However, during the night or on a Sunday, not many trains are riding and therefore, not many delays are being generated. Probably, we are making a lot of useless requests to the service: with many requests, we’ll get no updates. The only thing we are doing here is needlessly creating load on the service by sending many requests which aren’t returning anything useful.
In the latter case, it would be much more beneficiary if the service itself could initiate the communication. In other words, when a delay is created or removed, the service would start sending updated information to the client. This is exactly where duplex communication comes in. With duplex communication, messages can be sent by the client as well as the server. The server does not need to wait for a request to come in before it can send a message to the client; it can do so whenever necessary.
Advantages of duplex communication
The advantages of communicating duplex are the following:
- Scalability: if more than one client (in real world scenarios, this would of course be the case) is connecting to the service and they would all start polling the service needlessly, we are quite logically limiting the scalability of the service. Many requests won’t return anything useful. If we can make the service initiate the communication, the scalability will increase quite a lot. Since clients aren’t polling (and therefore creating load on the server), with the same hardware, we will be able to connect many more clients at the same time.
- Speed of the response: if we are polling with a 30 second interval, we may have to wait up to 30 seconds to get new information. In a duplex scenario where the server can send a message immediately when this is required, clients get the new messages more quickly.
Types of duplex communication in Silverlight
Duplex communication has been supported in Silverlight since version 2; however, some changes and additions have been made in Silverlight 3 and 4. Version 4 now supports 3 different types of duplex communication:
- HTTP Polling Duplex: this type was already available in Silverlight 2, but was changed somewhat in Silverlight 3 (actually it was made easier). We’ll look at this type in this article.
- Sockets: sockets were already available in Silverlight 2; some minor changes were made in Silverlight 4. We’ll focus on sockets in the next article of this series
- net.tcp binding: this entirely new binding, added in Silverlight 4, also supports duplex communication. net.tpc will be topic of the third article.
There is in fact a fourth way to do duplex communication and that’s the manual approach, as we explained earlier. In this type, we have to create the requests ourselves at certain intervals, that is, a polling mechanism. It’s hard to find an optimal solution here, since we need to decide on a poll time. Making this too low will result in faster updates but decrease the server’s scalability. Making it too high will result in the client not being updated frequently enough. Therefore, in most scenarios, the manual approach is not a good way to solve duplex communication problems.
Let’s now focus on polling duplex binding.
The HTTP Polling Duplex Binding in Silverlight
As already mentioned, normally a response is only sent by the server as a reaction on a request sent by the client. This is typical for HTTP communication. Duplex requires that the server can itself initiate communication, which is not supported by HTTP.
The HTTP polling duplex binding however allows us to do this bi-directional communication over HTTP. What happens behind the scenes is the following:
- The Silverlight client initiates the communication by sending an initial request to the service.
- After the client is registered with the service, it continuously starts polling the service for updates.
- Whenever the service has new messages to send to the client, they are queued up until a new poll arrives from the client.
Wait, let’s take a step back. We just said that polling is not a good idea and this solution actually does polling? Indeed it does. However, it does polling with a twist: continuously, requests are created on the network layer to see if new messages are ready, resulting in less overhead. This way, we are able to get updates almost immediately (since the polling is done continuously) and still scale quite well. The HTTP Polling Duplex is therefore not real duplexing: it creates an illusion of duplex communication by polling so frequently that it looks as if the messages are pushed from the service to the client.
The client actually polls the service until the PollTimeout occurs. The PollTimeout is by default set to 60 seconds. Once it reaches this timeout, a check is done to see if the session is still OK and if it is, an empty message is sent. After that, the polling starts again.
A second timeout exists: the InactivityTimeout. This occurs after 10 minutes of no data being sent. This is equal to 10 PollTimeouts occurring without data being sent over.
Internals of the HTTP Polling Duplex
The binding
The HTTP Polling Duplex uses the HttpPollingDuplex binding, which inherits from the BasicHttpBinding. Internally, it uses HTTP for its transport, the Net Duplex protocol for the initial connection from the client to the service and the WS Make Connection protocol to allow delivering messages from the service to the client.
Since the binding uses HTTP, it does not require any extra ports to be open (which is the case with sockets and net.tcp as we’ll see in the next article), making the HTTP Polling Duplex a perfect fit for duplex communication over the internet.
To enable this binding, we need to reference an assembly, System.ServiceModel.PollingDuplex, both in the service project and the Silverlight client. This assembly is available in the %Program Files%\Microsoft SDKs\Silverlight\v4.0\Libraries\Server directory for the service and %Program Files%\Microsoft SDKs\Silverlight\v4.0\Libraries\Client for the Silverlight client.
In configuration (web.config), we need to add the following code:
<system.serviceModel>
<extensions>
<bindingExtensions>
<add name="pollingDuplexHttpBinding"
type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,
System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</bindingExtensions>
</extensions>
…
<bindings>
<pollingDuplexHttpBinding>
</pollingDuplexHttpBinding>
</bindings>
…
<services>
<service name="DuplexTrains.Web.TrainDelayService"
behaviorConfiguration="DuplexTrains.Web.TrainDelayServiceBehavior">
<endpoint address="" binding="pollingDuplexHttpBinding"
contract="DuplexTrains.Web.ITrainDelayService" />
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
This code registers the assembly, creates the new binding and assigns the endpoint to use this binding.
The service
The service itself is a WCF service and resembles a regular WCF service quite a lot. There are however some particularities compared to a standard WCF service:
- It defines, next to the ServiceContract, a CallbackContract. This type will perform the callback to the client from the service instance.
- The operations are defined with the IsOneWay attribute set to True: this way, there will be no waiting for the response of the service call.
- GetCallbackChannel<T>: this is used in the service instance upon registering the client with the service. The service calls the GetCallbackChannel<T> on the OperationContext to get an instance of the callback contract. It should then store this client reference in a list so that when an update needs to be sent out, it can loop over that list and send a message to each of the connected clients.
Below is the service contract for our sample.
[ServiceContract(Namespace = "Silverlight", CallbackContract = typeof(ITrainDelayServiceClient))]
public interface ITrainDelayService
{
[OperationContract(IsOneWay = true)]
void Connect(string id);
}
[ServiceContract]
public interface ITrainDelayServiceClient
{
[OperationContract(IsOneWay = true)]
void SendTrainDelay(TrainDelay delayInfo);
}
As can be seen, the ServiceContract – ITrainDelayService - also has a CallbackContract defined. ITrainDelayService defines just one method, Connect(), which is used to allow Silverlight clients to connect and register with the service. This Connect() method has the IsOneWay attribute applied, which is a sign for the client code it does not have to wait for a response from the service. The CallbackContract – ITrainDelayServiceClient – defines the SendTrainDelay() method, which is the method that will be invoked by the service on the client.
The implementation code for the Connect method is shown below:
public class TrainDelayService : ITrainDelayService
{
public void Connect(string id)
{
ITrainDelayServiceClient client = OperationContext.Current.GetCallbackChannel<ITrainDelayServiceClient>();
TrainServiceRunner.Register(client);
}
}
In this Connect() method, we are getting an instance of the CallbackContract using the GetCallbackChannel() on the OperationContext. All clients that are connecting to the service are connecting to the same service instance. This one service instance will get the updates in (from when a file of a train delay was created or deleted, service calls, database triggers etc) and will also be the service instance that keeps a list of connected clients. This is shown in the code below (the Register() method used above):
public static void Register(ITrainDelayServiceClient client)
{
lock (myLock)
{
clients.Add(client);
}
}
When a train delay file is added or removed, the service code should run over the list and invoke the SendTrainDelay() method on each connected client.
//when a file is added, loop over the connected clients
lock (myLock)
{
foreach (var client in clients)
{
client.SendTrainDelay(trainDelay);
}
}
Clients
When we want our Silverlight application to communicate with the service, we start by adding a service reference. Visual Studio will create a proxy class but no configuration code is created. Therefore, some more code needs to be added manually: we need to create a CustomBinding instance, as shown in the following code:
EndpointAddress address = new EndpointAddress("http://localhost:2884/TrainDelayService.svc");
CustomBinding binding = new CustomBinding(
new PollingDuplexBindingElement(),
new BinaryMessageEncodingBindingElement(),
new HttpTransportBindingElement());
The client can subsequently register itself with the service and from then on, it will be waiting for incoming messages from the service. These callbacks execute on the UI thread, meaning we do not need to use the Dispatcher and can directly access UI elements. The code below shows that we invoke the Connect() method asynchronously and wait for callback on the SendTrainDelay() using the callback.
TrainDelayService.TrainDelayServiceClient client =
new TrainDelayService.TrainDelayServiceClient(binding, address);
client.SendTrainDelayReceived +=
new EventHandler<TrainDelayService.SendTrainDelayReceivedEventArgs>(client_SendTrainDelayReceived);
client.ConnectAsync(Guid.NewGuid().ToString());
Conclusion
In this first part of the series, we saw there are quite a lot of scenarios where we can benefit from duplex communication. The HttpPollingDuplex binding is a first of three ways to allow duplex from Silverlight. This type is a perfect fit for internet scenarios as it requires no extra ports to be opened. In the next part, we’ll look at sockets and their benefits.