Silverlight 2 WCF Polling Duplex Support – Part 3: The Client

Introduction

This is the third and final post in my current series on the support for duplex communication with a WCF service added to Silverlight 2 Beta 2. I should point out that this technology in its current form has been deemed by Microsoft as not yet fit for production applications and is therefore currently for evaluation only. In this post I introduce the client part of a sample application available for download (the download includes the server discussed in the previous post also) that uses the bi-directional communication discussed in part one. The sample application as a whole represents a simple client and server application that illustrates polling duplex communication in both directions. The client displays stock prices in a grid and animates updates received from the server. The user of the client application can also enter notes against a stock and have those notes propagate to all other connected clients via the server using polling duplex communication in the other direction. Here is a screenshot of the sample application running with the server and two connected clients, the same stock price updates are being displayed by both clients and the notes entered in Internet Explorer have been pushed to another instance of the client running in Firefox:

The Client

The layout of the Visual Studio 2008 solution is as follows:

There are three projects in the PollingDuplexDemo solution, the first is the Silverlight Application project StockClient that references the client-side version of System.ServiceModel.PollingDuplex.dll. The .xap file produced by building this project is referenced by the Silverlight ASP.NET server control in the StockClientTestPage.aspx file as part of the hosting StockClientHost Web Application project. The StockServer project is a Console Application project that self-hosts the polling duplex WCF service. As you can see from the image there are multiple startup projects, the first to start is the StockServer project along with the StockClientHost project shortly after.

Page.xaml and Page.xaml.cs

The markup in Page.xaml (see code download) defines the StocksGrid Silverlight DataGrid along with the rest of the UI elements, the code in Page.xaml.cs adds some initialisation code to the default constructor and handlers for some UI events. When the Silverlight Application loads and assigns an instance of the Page class defined across these files to the RootVisual property of the Application class, the constructor shown below is executed:

The StockTicker Class

It’s apparent from the code above that the instance of the StockTicker class is central to everything happening in the client application and indeed it’s the code in this class that implements the client-side PollingDuplex support:

namespace StockClient
{
public sealed class StockTicker
{
// Reference to layout instance that created the StockTicker
private readonly Dispatcher owner = null;
// Object used to let ThreadPool know to stop waiting and proceed
private readonly AutoResetEvent waitObject = new AutoResetEvent(false);
// Asynchronously begins an open operation on an ICommunicationObject with code to call EndOpen when it completes
private static readonly Action<ICommunicationObject> Open =
ico => ico.BeginOpen(iar => ico.EndOpen(iar), ico);
// Asynchronously begins a send operation on an IDuplexSessionChannel with code to call EndSend when it completes
private static readonly Action<IDuplexSessionChannel, Message> Send =
(idc, msg) => idc.BeginSend(msg, iar => idc.EndSend(iar), idc);
// Asynchronously begins a receive operation on an IDuplexSessionChannel with code to call an Action<Message> when it completes
private static readonly Action<IDuplexSessionChannel, Action<Message>> Receive =
(idc, act) => idc.BeginReceive(iar => act(idc.EndReceive(iar)), idc);
// Serializes instances of the Stock type before they are sent on the wire
private readonly DataContractSerializer stockSerializer = new DataContractSerializer(typeof(Stock));
// Channel for communication to WCF service
private IDuplexSessionChannel channel;
// List of stocks designed to be bound to a UI control
private readonly StockList stockList = new StockList();
public StockList StockList
{
get { return stockList; }
}
public StockTicker(Dispatcher owner)
{
if (owner == null)
{
throw new ArgumentNullException("owner");
}
this.owner = owner;
}
public void SubscribeDeltas()
{
// Create a channel factory capable of producing a channel of type IDuplexSessionChannel
IChannelFactory<IDuplexSessionChannel> factory =
new PollingDuplexHttpBinding().BuildChannelFactory<IDuplexSessionChannel>();
Open(factory);
// Address of the polling duplex server and creation of the channel to that endpoint
EndpointAddress endPoint = new EndpointAddress("http://localhost:10201/StockService");
channel = factory.CreateChannel(endPoint);
Open(channel);
// Create a message with the appropriate SOAPAction and asynchronously send it on the channel
Message message =
Message.CreateMessage(MessageVersion.Soap11, "Silverlight/IStockService/Register");
Send(channel, message);
// Use the thread pool to start only one asynchronous request to Receive messages from the server
// Only start another asynchronous request when a signal is received that the first thread pool thread has received something
ThreadPool.RegisterWaitForSingleObject(waitObject,
delegate { Receive(channel, CompleteReceive); },
null,
Timeout.Infinite,
false);
waitObject.Set();
}
public void Sync(Stock stock)
{
// Create a message with the appropriate SOAPAction and asynchronously send it on the channel with the serialized Stock as the body of the envelope
Message message =
Message.CreateMessage(MessageVersion.Soap11, "Silverlight/IStockService/Sync", stock, stockSerializer);
Send(channel, message);
}
private void CompleteReceive(Message message)
{
// Deserialize the body of the SOAP message into a Stock object
Stock stock = message.GetBody<Stock>();
// Queue a call to UpdateStockList on the Dispatcher of the thread that created this instance
Action<Stock> action = UpdateStockList;
owner.BeginInvoke(action, stock);
// Signal the thread pool to start another single asynchronous request to Receive messages from the server
waitObject.Set();
}
private void UpdateStockList(Stock delta)
{
// NOTE : CheckAccess is there but intellisense doesn't see it because of the [EditorBrowsable(EditorBrowsableState.Never)] attribute
if (!owner.CheckAccess())
{
throw new InvalidOperationException("The calling thread cannot access this method because a different thread owns it.");
}
// Check if this Stock is already in the collection
Stock existing = stockList.FirstOrDefault(s => s.Symbol == delta.Symbol);
if (existing == default(Stock))
{
// This delta is a new Stock
stockList.Add(new StockHighlight(delta));
}
else
{
// This delta is an existing Stock
existing.Ask = delta.Ask;
existing.Bid = delta.Bid;
if (!String.IsNullOrEmpty(delta.Notes))
{
existing.Notes = delta.Notes;
}
}
}
}
}

In order to create an instance of the StockTicker class a reference to an instance of the System.Threading.Dispatcher class is required. In the initialisation of the StockTicker instance in the Page class constructor the Dispatcher property inherited all the way from System.Windows.DependencyObject is used. This provides StockTicker with a queue to add tasks to for execution on the UI thread. It is essential to update collections that may be bound to UI controls (such as the StockList property) from the UI thread to avoid an invalid cross-thread access (UnauthorizedAccessException) exception; this restriction also applies to the animation of individual stock prices using Storyboards managed by the StockHighlight class. The stored Dispatcher reference is later used to queue calls to UpdateStockList to be executed on the UI thread from the CompleteReceive method and to check the correct thread has called UpdateStockList before updating the collection or the Stock properties that cause animation Storyboards to begin.

After the instance of StockTicker has been created in the constructor of the Page class the SubscribeDeltas method is called. This method performs all of the client setup necessary to begin a polling duplex session with the WCF service. The method body begins by creating an instance of the client-side version of PollingDuplexHttpBinding (see part one) and extracting an instance of a channel factory of type IDuplexSessionChannel from that binding. The factory is then opened asynchronously and the channel is created, the client then initiates communication by asynchronously opening the channel to the WCF service specified in EndpointAddress. The method progresses to create a SOAP 1.1 Message instance specifying the One Way Register method discussed in the previous post in the SOAPAction; the instance of this message is then sent asynchronously along the channel to the server. Finally the method uses an AutoResetEvent to signal a ThreadPool thread to begin one asynchronous request to receive messages pushed from the server. When a message is eventually received the CompleteReceive callback method is executed to queue a call back to the UI thread with the received Stock object before signaling the wait handle; this signal in turn prompts the thread pool to release another single asynchronous request to receive another message.

The Sync method creates a new SOAP 1.1 Message with a different SOAPAction and with the Stock parameter (containing the Notes entered by the user) as the body of the envelope serialised via the DataContractSerializer. The method then uses the same channel opened in SubscribeDeltas to asynchronously send the message to the server for propagation to all connected clients. The code for the WCF service methods being called here is discussed in part two, the make up of these asynchronous calls and their responses on the wire is discussed under the ‘Anatomy Of A Message Exchange’ heading In part one.

The UpdateStockList method checks the current thread as previously described and then searches the StockList collection for the current Stock object comparing Symbol properties. A new instance of the StockHighlight class is created to add to StockList if the received stock does not exist in the collection. As StockList is an ObservableCollection this insert will notify the DataGrid that it’s ItemsSource has changed and to update itself to honour the addition in the UI. If the stock already exists then only the relevant price and notes properties on the existing StockHighlight instance are updated, triggering the relevant animations. These updates in turn cause the PropertyChanged event from INotifyPropertyChanged implemented by the Stock class (see code download) to fire again causing the DataGrid to update the UI.

The StockHighlight Class

This class extends the basic Stock class overriding the Ask and Bid properties and exposing two public properties of type Brush:

Each cell in the Bid and Ask columns of the DataGrid declared in Page.xaml contains a StackPanel whose Background property is bound to the respective Brush property. This way each cell is able to play it’s own unique animation specific to the Stock displayed. These Brush properties need to be part of each individual Stock object rather than global resources so the whole column does not change colour when one stock changes price. The InitializeAnimation method associates animations of type ColorAnimationUsingKeyFrames with each of these Brushes for both positive and negative changes. The appropriate Storyboard to play is chosen in the Bid and Ask property setters based on the new value.

The most salient parts of the client code are now covered, download the solution, press F5 to start the server and one client automatically, you can then browse to StockClientTestPage.aspx using the same address from multiple clients in separate browsers once the service is running.

In the future it looks likely that the client side Silverlight 2 Polling Duplex API will be simplified to enable a less verbose method of connecting to and receiving pushed messages from a WCF service. This post talks more about the possibility of a DuplexReceiver<T> class for simple client scenarios in the future; it’s always prudent to know what such a class will be doing for you behind the scenes.

Further Reading

A few links on polling duplex support in Silverlight 2 Beta 2 you might find useful:

Gopinath Varadharajansaid

johnsaid

Great article! Its great to see how Duplex WCF contracts can be established programatically – allows for a great deal of flexibility. I was just wondering if you could post an example of client code for a WPF application connecting into this Stock server. I tried this when I had sl beta 2 installed (and .Net 3.5) and managed to get it working with no code change, however since moving to SL 2 RTW (and .NET 3.5SP1) pollingduplex is not supported in a WPF application. How would I get this working with something like WsDualHttpBinding in a WPF app?

Thanks,

John.

Peter McGrattansaid

You are correct: for a WPF client to connect to the same StockService you need to create a separate endpoint that uses WSDualHttpBinding, the client PollingDuplexHttpBinding is for Silverlight 2 use only.

With the addition of the second WSDualHttpBinding endpoint it is possible to have both Silverlight 2 and WPF clients receive the same Stock updates and send each other notes. I actually have an example finished that does exactly this and will be posting on this subject in the near future. Stay tuned…

Hope this helps,

Peter

Robsaid

Is it safe to make sequential asynchronous calls like factory.BeginOpen, channel.BeginOpen, and channel.BeginSend as you do in your example? It seems that you are opening yourself up to the possibility that you try to Send on an unopened channel or try to create a channel with an unopened factory. Shouldn’t the call to channel.BeginSend occur inside the async callback from channel.BeginOpen?

Peter McGrattansaid

You are absolutely correct, the possibility is a valid one. Although it works fine as is for the sample, in a production app the call to channel.BeginSend should occur inside the async callback from channel.BeginOpen. Open, Send and Receive should all be expanded to include exception handling also.

A large part of my motivation for the client post and code was the verbosity of the MSDN example, I wanted to reduce the client example to quickly show in very minimal code what needs to be opened/sent etc. in what order. Because of this I’m able to show the full StockTicker class above but with the possibility you describe, thanks for pointing this out.

Hope this helps,

Peter

Robertsaid

Thank you very much for posting this example. It runs as you described when I open a Firefox browser and IE browser, and register changes for the Notes, it changes on both browsers. I tried running a modified version of your example, using a WCF Service for the Polling “server” instead of a console app like you did. I basically send a message to the duplex, the duplex responds to the client, and I have a popup come up on the client, showing the response from the duplex. And for some reason, in my version with the WCF Service, the duplex only responds to the particular client that sent the message, not the other client(s). For example, if I send a message from Firefox, I get a popup response from the duplex service, but the popup does not show up in the IE browser. The reverse is also true. Is it possible that running this using a WCF Service instead of a console app could cause this discrepancy? I thought all clients connected to the service should receive any message that the duplex sends out (such as when the “Notes” get modified in your example)?

Peter McGrattansaid

Hopefully I can explain what’s happening here: Polling Duplex and indeed any other duplex support in WCF does not automatically push messages to all connected clients, this has to be done manually.

With the WCF service in the polling duplex sample from this blog a new instance of StockService is created per session (i.e. for each Silverlight client that connects to the PollingDuplexHttpBinding WCF endpoint a new instance of StockService is instantiated). Each session registers it’s interest in the DeltaReceived event of the singleton StockGenerator class in the body of the Register method. The event is raised approx every 250 milliseconds and the session’s handler (OnDeltaReceived) sends the delta to its respective client only via the stored channel reference.

With multiple clients there are multiple sessions and so multiple instances of StockService but only one instance of the singleton StockGenerator class where the stock deltas are generated.

Alsaid

Great example. However it seems to leak memory, I wan the sample code as is and my IE process memory jump immidiately and keep growing. It seems that the PollingDuplex library has a memory leak. -Thanks

Excellent series of articles!!!!! Deserves a 10/10 and nothing less!!! I have followed all 4 and chop and changed a bit in a project that I am playing with.

I have a problem though, I extended the Stock.cs by adding my own property and then added it as a datamember etc. with the highlights etc. When I update my value from the silverlight site (like u did with the add notes function),instead of updating my value, a whole new record gets displayed. I ran a debug on the StockServer and the code there functions correct as the list of stock does not increase, so it must be the client thats somehow thinking its a new record.

If a match on the symbol is positive then an update of the Ask, Bid and Notes (if the new Notes aren’t null) properties occurs, otherwise an insert occurs; check if this comparison is still valid for your changes.

Best Regards,

Peter

Alexeysaid

Is it possible to use PollingDuplex on simple TestPage.html without hosting on some website?
I start “StockClient” in the demo and it crashes:

An error occurred while trying to make a request to URI ‘http://localhost:10201/SilverlightStockService’. This could be due to attempting to access a service in a cross-domain way without a proper cross-domain policy in place, or a policy that is unsuitable for SOAP services. You may need to contact the owner of the service to publish a cross-domain policy file and to ensure it allows SOAP-related HTTP headers to be sent. Please see the inner exception for more details.

[…] 2 WCF Polling Duplex Support” articles: Part 1: Architecture, Part 2: The Server and Part 3: The Client. For an interface that boils down to 3 methods: Subscribe, Publish and NotifyReceived; and […]