Peter McGrattan’s Weblog

Silverlight, WCF, ASP.NET, AJAX, Graphics, RIA

Posts Tagged ‘Silverlight’

Customizing the Silverlight 2 ItemsControl with Templates

Posted by petermcg on February 15, 2009

(Code download)

The ItemsControl in Silverlight 2 is the base type for a number of list based UI controls:

ItemsControl Derived Types

It’s at a place in it’s class hierarchy that means it provides just enough to get you going with list output. Controls further down the inheritance hierarchy from ItemsControl tend to specialise it’s functionality by providing the ability to select or edit items in the list in slightly distinct ways. The items in an ItemsControl can be defined in Xaml or generated dynamically from the ItemsSource property in code.

Every aspect of the ItemsControl’s appearance from the exterior of the control, the panel that surrounds it’s items and the items themselves can be customized based on a pre-defined pattern or ‘template’ that you specify. Three properties on the ItemsControl allow you to specify three different types of template to describe three different aspects of the appearance of the final list:

Property Name

Type

Default Content

Template System.Windows.Controls.ControlTemplate ItemsPresenter containing StackPanel
ItemsPanel System.Windows.Controls.ItemsPanelTemplate StackPanel containing ContentPresenter per item
ItemTemplate System.Windows.DataTemplate ContentPresenter containing Grid

 
These defaults can be visualised in the following control hierarchy:

Default ItemsControl Template Output

The ‘Template’ property is inherited from Control and appropriately allows you to specify a ControlTemplate to define the appearance of everything about the ItemsControl outside it’s ItemsPresenter. The other two properties ‘ItemsPanel’ and ‘ItemTemplate’ are defined by the ItemsControl class itself and are concerned with the appearance of items inside the ItemsControl. More specifically the ‘ItemsPanel’ property controls the appearance of the panel that surrounds all the item’s ContentPresenters, the ‘ItemTemplate’ property customizes the appearance of the contents of each ContentPresenter.

Customizing the Default Templates

The following example shows how to customize the value of each of these three properties to produce a different control hierarchy. The sample application attached to this post makes use of the ItemsControl to position images not vertically in a StackPanel but at specific coordinates within a Canvas. The Xaml from the sample application is reproduced below along with the subsequent control hierarchy:

<UserControl x:Class="ItemsControlImages.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:app="clr-namespace:ItemsControlImages">

    <UserControl.Resources>
        <ControlTemplate x:Key="ControlTemplateKey">
            <Border x:Name="ControlTemplate_ValueOf_Template">
                <ItemsPresenter />
            </Border>
        </ControlTemplate>

        <ItemsPanelTemplate x:Key="ItemsPanelKey">
            <Grid x:Name="ItemsPanelTemplate_ValueOf_ItemsPanel" />
        </ItemsPanelTemplate>

        <DataTemplate x:Key="ItemTemplateKey">
            <Canvas x:Name="DataTemplate_ValueOf_ItemTemplate" >
                <Image Canvas.Top="{Binding Location.Y}"
                       Canvas.Left="{Binding Location.X}"
                       Source="{Binding FlagImage}" />

                <StackPanel Canvas.Top="{Binding Location.Y}"
                            Canvas.Left="{Binding Location.X}">
                    <TextBlock Text="{Binding Title}" />
                    <TextBlock Text="{Binding Location}" />

                    <StackPanel.RenderTransform>
                        <TranslateTransform Y="-32.0" />
                    </StackPanel.RenderTransform>
                </StackPanel>
            </Canvas>
        </DataTemplate>
    </UserControl.Resources>

    <Canvas x:Name="LayoutRoot">
        <Canvas.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FFB2C6D5"/>
                <GradientStop Color="#FF1483D9" Offset="1"/>
            </LinearGradientBrush>
        </Canvas.Background>

        <ItemsControl Template="{StaticResource ControlTemplateKey}"
                      ItemsPanel="{StaticResource ItemsPanelKey}"
                      ItemTemplate="{StaticResource ItemTemplateKey}">

            <app:Country Title="Argentina" Location="56,218" FlagImage="Images/ARG.png" />
            <app:Country Title="China" Location="368,66" FlagImage="Images/CHN.png" />
            <app:Country Title="Ireland" Location="192,90" FlagImage="Images/IRE.png" />
            <app:Country Title="New Zealand" Location="404,225" FlagImage="Images/NZ.png" />
            <app:Country Title="United States" Location="40,80" FlagImage="Images/USA.png" />

        </ItemsControl>
    </Canvas>
</UserControl>

This Xaml produces the following control hierarchy:

Customized ItemsControl Template Output

The contents of the three custom templates are defined in the Resources section of the UserControl and bound to the relevant properties of the instance of ItemsControl. Firstly the ControlTemplate specified in the Template property has been used to surround the ItemsPresenter with a blank Border. Secondly the ItemsPanelTemplate specified in the ItemsPanel property means the ItemsPresenter now has a Grid as it’s content rather than a StackPanel. Finally the DataTemplate specified in the ItemTemplate property has completely replaced the default Grid with a Canvas containing an Image and a StackPanel. The contents of the DataTemplate are interspersed with Binding expressions to extract data from each item in the ItemsControl, in this case instances of the simple Country class shown below:

using System.ComponentModel;
using System.Windows;

namespace ItemsControlImages
{
    public class Country : INotifyPropertyChanged
    {
        private string title;
        private string flagImage;
        private Point location;

        public string Title
        {
            get
            {
                return this.title;
            }
            set
            {
                if ((object.ReferenceEquals(this.title, value) != true))
                {
                    this.title = value;
                    this.RaisePropertyChanged("Title");
                }
            }
        }

        public string FlagImage
        {
            get
            {
                return this.flagImage;
            }
            set
            {
                if ((object.ReferenceEquals(this.flagImage, value) != true))
                {
                    this.flagImage = value;
                    this.RaisePropertyChanged("FlagImage");
                }
            }
        }

        public Point Location
        {
            get
            {
                return this.location;
            }
            set
            {
                if ((this.location.Equals(value) != true))
                {
                    this.location = value;
                    this.RaisePropertyChanged("Location");
                }
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler propertyChanged = this.PropertyChanged;

            if (propertyChanged != null)
            {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}

This all conspires to produce the following UI:

Positioned ItemsControl Images

Note even though the Images are in an ItemsControl they are positioned at the coordinates shown by setting the value of the Left and Top attached properties of their parent Canvas to the value of the X and Y coordinates from the custom Location property via the Bindings specified in the DataTemplate assigned to the ItemsControl’s ItemTemplate property.

The images used in the sample application are from this article on WikiMedia and the control hierarchy images were produced from screenshots of the excellent Silverlight Spy 2 which I highly recommend.

Advertisements

Posted in ItemsControl, Silverlight, XAML | Tagged: , , , , , | 14 Comments »

Silverlight 2 WCF Polling Duplex Support – Part 4: Adding a WPF Client

Posted by petermcg on November 19, 2008

(Code download)

Introduction

This post attempts to highlight some of the intricacies involved in connecting a WPF Client to the existing Silverlight 2 Polling Duplex demo previously published on this blog.  To recap, the demo consists of a WCF service designed to push Stock data to previously connected Silverlight 2 clients over Http.  In a similar scenario to a chat application, the Silverlight 2 clients can also send ‘Note’ messages back to the server and have them propagated to other connected clients.

The aim of this post is to make the demo capable of also supporting connections from WPF clients.  Success is defined by enabling WPF clients to connect to the same WCF service, receive the same Stock updates at the same time as the Silverlight 2 clients and having Notes synchronized across all connected clients regardless of their underlying technology.  Below is a link to a screenshot of the final version of this demo running, shown is the Console Application hosting the WCF service with two Silverlight clients and one WPF client connected:

Server and All Clients Running

PollingDuplexHttpBinding and WPF clients

Up until this point the demo application has solely supported pushing data to Silverlight 2 clients, defining a PollingDuplexHttpBinding endpoint on the server side and creating a channel factory as part of the Silverlight 2 client as shown below:

// Create a channel factory capable of producing a channel of type IDuplexSessionChannel
IChannelFactory<IDuplexSessionChannel> factory = new PollingDuplexHttpBinding().BuildChannelFactory<IDuplexSessionChannel>();

However, attempting to use PollingDuplexHttpBinding for this purpose in a WPF application (as in the code above) currently results in a NotSupportedException exception being raised with a full explanation and even some architectural advice:

Polling Duplex NotSupportedException

PollingDuplexHttpBinding cannot currently be used in non-Silverlight clients without running into this exception.  In order to extend the demo to support pushing Stock data to a WPF client through Http the guidance from the above exception is to consider WSDualHttpBinding.  This binding predates Silverlight and is the closest alternative for achieving equivalent results to PollingDuplexHttpBinding in a non-Silverlight client application.  A look at the BindingElement objects each binding encapsulates reveals WSDualHttpBinding is actually a much more mature binding than PollingDuplexHttpBinding:

WSDualHttpBinding PollingDuplexHttpBinding
TransactionFlowBindingElement PollingDuplexBindingElement
ReliableSessionBindingElement TextMessageEncodingBindingElement
SymmetricSecurityBindingElement HttpTransportBindingElement
CompositeDuplexBindingElement  
OneWayBindingElement  
TextMessageEncodingBindingElement  
HttpTransportBindingElement  
The WSDualHttpBinding Endpoint

The outcome of acting on the aforementioned advice in the demo application is the addition of a new endpoint to the WCF service exposed from the StockServer Console Application.  This could easily be specified entirely in a configuration file (see this post for how to configure the Silverlight policy and duplex endpoints) but in the download is achieved in code as shown below:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace StockServer
{
    public class StockServiceHost : ServiceHost
    {
        public StockServiceHost(object singletonInstance, params Uri[] baseAddresses)
            : base(singletonInstance, baseAddresses)
        {
        }

        public StockServiceHost(Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
        }

        protected override void InitializeRuntime()
        {
            this.AddServiceEndpoint(
                typeof(IPolicyProvider),
                new WebHttpBinding(),
                new Uri("http://localhost:10201/")).Behaviors.Add(new WebHttpBehavior());

            this.AddServiceEndpoint(
                typeof(IStockService),
                new PollingDuplexHttpBinding(),
                new Uri("http://localhost:10201/SilverlightStockService"));

            this.AddServiceEndpoint(
                typeof(IStockService),
                new WSDualHttpBinding(WSDualHttpSecurityMode.None),
                new Uri("http://localhost:10201/WpfStockService"));

            base.InitializeRuntime();
        }
    }
}

Although the WSDualHttpBinding endpoint is registered using a different URI, the endpoint is simply just another entry point into the same WCF service called by the existing Silverlight 2 clients using PollingDuplexHttpBinding.  Before you can push data from a WCF duplex service to a client, the client must initiate the session by calling the service.  The Register method serves this purpose in the demo application and the only change to the code in the WCF service itself (although there are no method signature changes) is in this method as shown below:

public void Register(Message message)
{
    client = new StockClient(OperationContext.Current.GetCallbackChannel<IStockClient>(),
                                  "StockClient/IStockService/Receive",
                                  message.Version);
    client.Faulted += OnClientFaulted;

    InitialReply();

    StockGenerator.Instance.DeltaReceived += OnDeltaReceived;
}

The change is minor but essential: extracting the MessageVersion from the initial message sent by the client and passing it to the constructor of the StockClient object along with a reference to the channel back to that client.  The variety of MessageVersion used by each of the bindings in the demo application is as follows:

Binding MessageVersion
WSDualHttpBinding
MessageVersion.Soap12WSAddressing10
PollingDuplexHttpBinding
MessageVersion.Soap11

Messages sent between WSDualHttpBinding endpoints use the SOAP 1.2 protocol; between PollingDuplexHttpBinding endpoints the SOAP 1.1 protocol is employed.  There is a clue in the name of MessageVersion. Soap12WSAddressing10 that WSDualHttpBinding also requires addressing headers to support reliable messaging sessions via ReliableSessionBindingElement.  In the case of these two bindings, attempting to use a mismatch of MessageVersion and binding, for example MessageVersion.Soap11 for messages sent to a WSDualHttpBinding endpoint, results in an InvalidOperationException as shown below:

MessageVersion InvalidOperationException

Storing the message version along with the channel reference in an instance of the StockClient class (see code download) ensures the reply messages are pushed back to the client in the format the relevant binding expects.  The rest of the WCF service remains the same: a new instance of StockService is created per session regardless of the endpoint used.  Each session registers it’s interest in the DeltaReceived event of the singleton StockGenerator class.  This event is raised approx every 250 milliseconds and each session’s handler (OnDeltaReceived) sends the same generated delta to its respective client via the stored channel reference using the appropriate message protocol.

The WPF Client Application

In a good advertisement for how nearly all of what you write in Silverlight 2 can be used without modification in WPF, the code in the WPF client application is extremely similar to that contained in the Silverlight 2 client project.  The markup in StockWindow.xaml and code in StockWindow.xaml.cs is virtually identical to Page.xaml and Page.xaml.cs in the Silverlight 2 project.

One difference between the WPF and Silverlight client code is the WPF DataGrid which is from the October 2008 Release of the WPF Toolkit available from codeplex.  Another difference already highlighted is that PollingDuplexHttpBinding cannot be used in a non-Silverlight client application.  In its place we use WSDualHttpBinding on the WPF client side also and due to this binding’s maturity the code in StockTicker.cs is less verbose as shown below:

using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Windows.Threading;
using Common;

namespace WpfStockClient
{
    public sealed class StockTicker : IStockClient
    {
        // Reference to layout instance that created the StockTicker
        private readonly Dispatcher owner = null;

        // Serializes instances of the Stock type before they are sent on the wire
        private readonly DataContractSerializer stockSerializer = new DataContractSerializer(typeof(Stock));

        // Proxy for communication to WCF service
        private IStockService proxy;

        // 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()
        {
            EndpointAddress endPoint = new EndpointAddress("http://localhost:10201/WpfStockService");

            proxy = new DuplexChannelFactory<IStockService>(this,
                                                            new WSDualHttpBinding(WSDualHttpSecurityMode.None),
                                                            endPoint).CreateChannel();

            proxy.Register(Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "StockClient/IStockService/Register"));
        }

        public void Receive(Message message)
        {
            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);
        }

        public void Sync(Stock stock)
        {
            // Create a message with the appropriate SOAPAction and asynchronously send it via the proxy with the serialized Stock as the body of the envelope
            Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "StockClient/IStockService/Sync", stock, stockSerializer);
            proxy.Sync(message);
        }

        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;
                }
            }
        }
    }
}

There are many similarities between the above code and the Silverlight 2 version; the changes are all centred on the body of the SubscribeDeltas method.  This is where the client instance of the WSDualHttpBinding class is instantiated and used to establish a composite duplex channel (two one-way channels) with the new endpoint at the address exposed in the WCF service.  The first parameter passed to the DuplexChannelFactory<IStockService>  constructor specifies ‘this’ as the instance of the IStockService interface to receive the pushed messages from the WCF service in the Receive method.  After the channel is created the initial message is sent using the correct MessageVersion as described earlier.

Linked Common Files

Those class files that are identical across both client projects are added to the Common Solution folder and are referenced using links from the respective projects where they are used:

Common Files Linked

The highlighted classes are written in one place and as such are assured of being the same structure throughout the solution (and where relevant on both sides of the wire as a consequence).  As the files are linked, the code they contain will be compiled into IL that targets the respective version of the CLR implied by the type of project the classes are linked from.  For the WPF project the linked classes will be compiled into an assembly that targets the desktop CLR, for the Silverlight project the linked classes will be compiled into an assembly that targets the more compact CoreCLR.  Linking common files in this manner is one solution to the problem normally solved by using a class library when a common CLR is targeted by all projects.

Animating the Stock Price changes

Illustrating changes to Stock price instances displayed in the DataGrid by creating the illusion that the DataGrid cell background is changing color takes less code in WPF than in Silverlight.  The code for this animation for both WPF and Silverlight 2 is contained in the StockHighlight class.  The entire contents of the StockHighlight.cs file is shown below, the code between the #if and #endif directives is compiled only if the SILVERLIGHT symbol is defined as it is in the Build tab of project properties on all default Silverlight 2 projects:

using System;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Common
{
    [DataContract(Name = "Stock", Namespace = "urn:petermcg.wordpress.com")]
    public class StockHighlight : Stock
    {
        public SolidColorBrush AskHighlight { get; private set; }
        public SolidColorBrush BidHighlight { get; private set; }

#if SILVERLIGHT
        private Storyboard askPosStory = new Storyboard();
        private Storyboard askNegStory = new Storyboard();
        private Storyboard bidPosStory = new Storyboard();
        private Storyboard bidNegStory = new Storyboard();
#endif

        private static readonly Duration DefaultDuration = new Duration(TimeSpan.FromSeconds(2.0));
        private static readonly Color DefaultColor = Colors.Transparent;

        public StockHighlight(Stock stock)
        {
            base.Ask = stock.Ask;
            base.Bid = stock.Bid;
            base.Notes = stock.Notes;
            base.Symbol = stock.Symbol;

            InitializeAnimation();
        }

        private void InitializeAnimation()
        {
            // Default highlight color to Transparent
            this.AskHighlight = new SolidColorBrush(DefaultColor);
            this.BidHighlight = new SolidColorBrush(DefaultColor);

#if SILVERLIGHT
            InitializeAnimation(AskHighlight, askPosStory, askNegStory);
            InitializeAnimation(BidHighlight, bidPosStory, bidNegStory); 
#endif
        }

#if SILVERLIGHT
        private static void InitializeAnimation(SolidColorBrush brush, Storyboard pos, Storyboard neg)
        {
            // Set up a unique Positive and Negative ColorAnimationUsingKeyFrames for  price
            ColorAnimationUsingKeyFrames posAnim = new ColorAnimationUsingKeyFrames();
            posAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = TimeSpan.FromSeconds(0.0), Value = Colors.Green });
            posAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = DefaultDuration.TimeSpan, Value = DefaultColor });
            posAnim.Duration = DefaultDuration;

            ColorAnimationUsingKeyFrames negAnim = new ColorAnimationUsingKeyFrames();
            negAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = TimeSpan.FromSeconds(0.0), Value = Colors.Red });
            negAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = DefaultDuration.TimeSpan, Value = DefaultColor });
            negAnim.Duration = DefaultDuration;

            // Add animations to storyboard
            pos.Children.Add(posAnim);
            neg.Children.Add(negAnim);

            // Target the  color animations to the  brush
            Storyboard.SetTarget(posAnim, brush);
            Storyboard.SetTarget(negAnim, brush);

            // Target the property of the  brush to animate
            Storyboard.SetTargetProperty(posAnim, new PropertyPath(SolidColorBrush.ColorProperty));
            Storyboard.SetTargetProperty(negAnim, new PropertyPath(SolidColorBrush.ColorProperty));
        }

        private void BeginHighlight(Storyboard storyBoard)
        {
            storyBoard.Begin();
        }
#else
        private void BeginHighlight(SolidColorBrush brush, Color startColor)
        {
            ColorAnimationUsingKeyFrames anim = new ColorAnimationUsingKeyFrames();
            anim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = TimeSpan.FromSeconds(0.0), Value = startColor });
            anim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = DefaultDuration.TimeSpan, Value = DefaultColor });
            anim.Duration = DefaultDuration;

            brush.BeginAnimation(SolidColorBrush.ColorProperty, anim);
        }
#endif
        public override double Ask
        {
            get
            {
                return base.Ask;
            }
            set
            {
                if ((base.Ask.Equals(value) != true))
                {
#if SILVERLIGHT
                    BeginHighlight((value > this.Ask) ? askPosStory : askNegStory);
#else
                    BeginHighlight(AskHighlight, (value > this.Ask) ? Colors.Green : Colors.Red);
#endif

                    base.Ask = value;

                    this.RaisePropertyChanged("Ask");
                }
            }
        }

        public override double Bid
        {
            get
            {
                return base.Bid;
            }
            set
            {
                if ((base.Bid.Equals(value) != true))
                {
#if SILVERLIGHT
                    BeginHighlight((value > this.Bid) ? bidPosStory : bidNegStory);
#else
                    BeginHighlight(BidHighlight, (value > this.Bid) ? Colors.Green : Colors.Red);
#endif

                    base.Bid = value;

                    this.RaisePropertyChanged("Bid");
                }
            }
        }
    }
}

The targets of the animation are the two public SolidColorBrush properties, AskHighlight and BidHighlight.  The Color dependency property of these brushes is animated using color key frame animation from green for a positive change or red for a negative change.  The animation for both WPF and Silverlight begins with a call to the BeginHighlight method when the corresponding Ask or Bid price property changes.  The SILVERLIGHT debug symbol makes it clear to see the extra Storyboard objects and extra initialization required in the InitializeAnimation method to achieve the desired effect in Silverlight 2.  You can see the references to these AskHighlight and BidHighlight properties in the WPF XAML below, also notice the xmlns:data for the DataGrid from the WPF Toolkit:

<Window x:Class="WpfStockClient.StockWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="http://schemas.microsoft.com/wpf/2008/toolkit"
    Title="Stock Window" Width="640" Height="520">

    <Grid x:Name="LayoutRoot" Background="White"  Margin="5,5,5,5">
        <Grid.RowDefinitions>
            <RowDefinition MaxHeight="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width=".80*" />
            <ColumnDefinition Width=".20*" />
        </Grid.ColumnDefinitions>

        <TextBox x:Name="Notes" Grid.Row="0" Grid.Column="0" Margin="0,0,5,0" />
        <Button x:Name="AddNotes" Click="AddNotes_Click" Content="Add Notes" Grid.Row="0" Grid.Column="1" Margin="5,0,0,0" />

        <data:DataGrid x:Name="StocksGrid"
                       AutoGenerateColumns="False"
                       Grid.Row="1"
                       Grid.ColumnSpan="2"
                       GridLinesVisibility="Horizontal"
                       HeadersVisibility="Column"
                       RowBackground="AliceBlue"
                       CanUserResizeColumns="False"
                       SelectionChanged="StocksGrid_SelectionChanged"
                       SelectionMode="Single"
                       Margin="0,5,0,0">
            <data:DataGrid.Columns>
                <data:DataGridTemplateColumn Header="Symbol">
                    <data:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Symbol}" />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellTemplate>
                </data:DataGridTemplateColumn>
                <data:DataGridTemplateColumn Header="Bid">
                    <data:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Background="{Binding BidHighlight}">
                                <TextBlock Text="{Binding Bid}" />
                            </StackPanel>
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellTemplate>
                </data:DataGridTemplateColumn>
                <data:DataGridTemplateColumn Header="Ask">
                    <data:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Background="{Binding AskHighlight}">
                                <TextBlock Text="{Binding Ask}" />
                            </StackPanel>
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellTemplate>
                </data:DataGridTemplateColumn>
                <data:DataGridTemplateColumn MinWidth="150" Header="Notes">
                    <data:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Notes}" />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellTemplate>
                </data:DataGridTemplateColumn>
            </data:DataGrid.Columns>
        </data:DataGrid>
    </Grid>
</Window>

For more detail and to see the application running the entire solution can be downloaded using the link at the start of this post.

Posted in PollingDuplex, Silverlight, WCF, WPF | Tagged: , , , , , , , , | 9 Comments »

Debugging Polling Duplex on localhost with ipv4.fiddler

Posted by petermcg on September 30, 2008

This post takes a brief look at the options for capturing localhost HTTP traffic in the superb Fiddler2 tool but in particular demonstrates how this can be achieved using the less-renowned ipv4.fiddler keyword in a Silverlight 2 polling duplex debugging session.

HTTP traffic sent to or originating from http://localhost or http://127.0.0.1 by Internet Explorer and the .NET Framework is not captured in Fiddler2 by default because the requests and responses do not pass through the WinInet proxy that it registers.  A few simple workarounds do exist to commonly facilitate the capture of this traffic (without customizing Fiddler’s rules) however:

  • Using 127.0.0.1 with a dot suffix
  • Using the Machine Name
  • Using ipv4.fiddler

The rest of this post shows the results of using each of these methods in an attempt to achieve a complete trace of all HTTP traffic being sent and received between my sample Silverlight 2 Beta 2 polling duplex client and server application, the results were quite interesting.  The sample application consists of three projects:

  • The Silverlight 2 Beta 2 client polling duplex application
  • The Web Application hosting the Silverlight client
  • The WCF polling duplex service hosted in a Console Application

The server manually handles the Silverlight client access policy requirements and messages are sent in both directions by both parties.  Capturing all the traffic between the client and server in Fiddler2 requires the base http://localhost url to be manipulated at design time according to the appropriate workaround in each of the three projects.  Let’s take a look at the process using each workaround in turn and the ensuing results:

1. Using 127.0.0.1 with a dot suffix

I first read about this technique here and it does work in a number of more common scenarios, let’s see how it fares in this scenario:

A. Silverlight Client

Firstly the manipulation of the default http://localhost url used to connect to the server duplex service from the Silverlight 2 client project:

// From this
EndpointAddress endPoint = new EndpointAddress("http://localhost:10201/StockService");
// To this - Note the period after localhost
EndpointAddress endPoint = new EndpointAddress(http://127.0.0.1.:10201/StockService);

B. Hosting Web Application

Secondly the same change to the Start URL for the web application that hosts the Silverlight 2 client:

Start Url localhost dot suffix

C. WCF Service Console Application

Lastly a change to the base address of the WCF polling duplex service in the server’s configuration file:

Base address localhost dot suffix

D. Results in Fiddler

Fiddler localhost dot suffix

The screenshot above shows the results of our efforts using this workaround.  The session gets as far as the GET request for the built-in clientaccesspolicy.xml file.  Due to our manually added suffix a HTTP 400 Bad Request – Invalid Hostname response is returned.  This HTTP response is eventually translated into the following exception in the Silverlight client:

Exception localhost dot suffix

2. Using the Machine Name

This is a technique listed in the relevant help section on the Fiddler2 website, lets see what results this workaround yields:

A. Silverlight Client

Again we start with the manipulation of the default http://localhost url used to connect to the server duplex service from the Silverlight 2 client project:

// From this
EndpointAddress endPoint = new EndpointAddress("http://localhost:10201/StockService");
// To this (i.e. Environment.MachineName)
EndpointAddress endPoint = new EndpointAddress(http://vgnar21s:10201/StockService);

B. Hosting Web Application

Again, the same change to the Start URL for the web application that hosts the Silverlight 2 client:

Start Url Machine Name

C. WCF Service Console Application

Lastly a change to the base address of the WCF polling duplex service in the server’s configuration file:

Base address Machine Name

D. Results in Fiddler

Fiddler Machine Name

The screenshot above shows the results of our efforts using this workaround.  The session does not get past the request for the aspx page that hosts the Silverlight application and a HTTP 502 Connection Failed response is returned.  This HTTP response message eventually appears as the following error in Internet Explorer:

IE Machine Name

3. Use ipv4.fiddler keyword

At this stage the two previous workarounds have both proven unsuccessful, let’s try the same process this time with another technique listed in the relevant help section on the Fiddler2 website – use of the ipv4.fiddler keyword.  The ipv4.fiddler keyword requires Fiddler v2.1.8 or later to be running, I should also mention the ipv6.fiddler keyword (not covered in this post) for IPV6 debugging.

A. Silverlight Client

Firstly the manipulation of the default http://localhost url used to connect to the server duplex service in the Silverlight 2 client project:

// From this
EndpointAddress endPoint = new EndpointAddress("http://localhost:10201/StockService");
// To this (Fiddler keyword)
EndpointAddress endPoint = new EndpointAddress(http://ipv4.fiddler:10201/StockService);

B. Hosting Web Application

Secondly the same change to the Start URL for the web application that hosts the Silverlight 2 client:

Start Url ipv4

C. WCF Service Console Application

Lastly a change to the base address of the WCF polling duplex service in the server’s configuration file:

Base address ipv4

D. Results in Fiddler

Fiddler ipv4

Success – this workaround accomplishes exactly what we set out to achieve, the screenshot above shows the capture of all HTTP traffic going between the client and server.  It shows everything from the initial request for the hosting aspx page, downloading the .xap file, the request and custom self-hosted response for the clientaccesspolicy.xml file and finally the actual polling duplex activity on the wire.  Note the content of the User-defined column, this is as a result of turning on ‘Show Time-to-Last-Byte’ and ‘Show Response Timestamp’ under the Rules > Performance menu in Fiddler2.

For a deep-dive into exactly what these polling duplex requests and responses contain, please see the ‘Anatomy of a Bi-Directional Message Exchange’ heading in Part 1 – Architecture of my series of posts on polling duplex in Silverlight 2.

Posted in Debugging, Fiddler, PollingDuplex, Silverlight | Tagged: , , | 4 Comments »

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

Posted by petermcg on September 3, 2008

Edited on 20/October/2008 : Code download updated to support Silverlight 2 RTW

(Code download)

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:

Sample Application Running

The Client

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

PollingDuplexDemo Solution Explorer

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:

namespace StockClient
{
    public partial class Page : UserControl
    {
        private readonly StockTicker stockTicker = null;

        public Page()
        {
            InitializeComponent();

            stockTicker = new StockTicker(this.Dispatcher);

            // Bind DataGrid to ObservableCollection
            StocksGrid.ItemsSource = stockTicker.StockList;

            // Get deltas from stock delta service
            stockTicker.SubscribeDeltas();
        }

        private void AddNotes_Click(object sender, RoutedEventArgs e)
        {
            Stock selectedStock = StocksGrid.SelectedItem as Stock;

            if (selectedStock != null)
            {
                selectedStock.Notes = Notes.Text;

                stockTicker.Sync(selectedStock);
            }
        }

        private void StocksGrid_SelectionChanged(object sender, EventArgs e)
        {
            Stock selectedStock = StocksGrid.SelectedItem as Stock;

            if (selectedStock != null && !String.IsNullOrEmpty(selectedStock.Notes))
            {
                Notes.Text = selectedStock.Notes;
            }
        }
    }
}
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:

namespace StockClient
{
    [DataContract(Name = "Stock", Namespace = "urn:petermcg.wordpress.com")]
    public class StockHighlight : Stock
    {
        public Brush AskHighlight { get; private set; }
        public Brush BidHighlight { get; private set; }

        private Storyboard askPosStory = new Storyboard();
        private Storyboard askNegStory = new Storyboard();
        private Storyboard bidPosStory = new Storyboard();
        private Storyboard bidNegStory = new Storyboard();

        private static readonly Duration DefaultDuration = new Duration(TimeSpan.FromSeconds(2.0));
        private static readonly Color DefaultColor = Colors.Transparent;

        public StockHighlight(Stock stock)
        {
            base.Ask = stock.Ask;
            base.Bid = stock.Bid;
            base.Notes = stock.Notes;
            base.Symbol = stock.Symbol;

            InitializeAnimation();
        }

        private void InitializeAnimation()
        {
            // Default highlight color to Transparent
            this.AskHighlight = new SolidColorBrush(DefaultColor);
            this.BidHighlight = new SolidColorBrush(DefaultColor);

            InitializeAnimation(AskHighlight, askPosStory, askNegStory);
            InitializeAnimation(BidHighlight, bidPosStory, bidNegStory);
        }

        private static void InitializeAnimation(Brush brush, Storyboard pos, Storyboard neg)
        {
            // Set up a unique Positive and Negative ColorAnimationUsingKeyFrames for  price
            ColorAnimationUsingKeyFrames posAnim = new ColorAnimationUsingKeyFrames();
            posAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = TimeSpan.FromSeconds(0.0), Value = Colors.Green });
            posAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = DefaultDuration.TimeSpan, Value = DefaultColor });
            posAnim.Duration = DefaultDuration;

            ColorAnimationUsingKeyFrames negAnim = new ColorAnimationUsingKeyFrames();
            negAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = TimeSpan.FromSeconds(0.0), Value = Colors.Red });
            negAnim.KeyFrames.Add(new LinearColorKeyFrame() { KeyTime = DefaultDuration.TimeSpan, Value = DefaultColor });
            negAnim.Duration = DefaultDuration;

            // Add animations to storyboard
            pos.Children.Add(posAnim);
            neg.Children.Add(negAnim);

            // Target the  color animations to the  brush
            Storyboard.SetTarget(posAnim, brush);
            Storyboard.SetTarget(negAnim, brush);

            // Target the property of the  brush to animate
            Storyboard.SetTargetProperty(posAnim, new PropertyPath("(Brush.Color)"));
            Storyboard.SetTargetProperty(negAnim, new PropertyPath("(Brush.Color)"));
        }

        public override double Ask
        {
            get
            {
                return base.Ask;
            }
            set
            {
                if ((base.Ask.Equals(value) != true))
                {
                    Func<double, double, Storyboard> GetAskStory = (o, n) => (n > o) ? askPosStory : askNegStory;
                    GetAskStory(this.Ask, value).Begin();

                    base.Ask = value;
                }
            }
        }

        public override double Bid
        {
            get
            {
                return base.Bid;
            }
            set
            {
                if ((base.Bid.Equals(value) != true))
                {
                    Func<double, double, Storyboard> GetBidStory = (o, n) => (n > o) ? bidPosStory : bidNegStory;
                    GetBidStory(this.Bid, value).Begin();

                    base.Bid = value;

                    this.RaisePropertyChanged("Bid");
                }
            }
        }
    }
}

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:

MSDN – Access a Duplex Service with the Channel Model

MSDN – How to: Build a Duplex Service

MSDN – Accessing Duplex Services

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

Pushing Data to a Silverlight Client with a WCF Duplex Service – Part II

Detailed Overview of Silverlight 2 Beta 2 Web Service Features

"Pushing" Data to a Silverlight Application

Posted in PollingDuplex, Silverlight, WCF | Tagged: , , , , , , , | 16 Comments »

Silverlight 2 WCF Polling Duplex Support – Part 2: The Server

Posted by petermcg on September 3, 2008

Edited on 20/October/2008 : Code download updated to support Silverlight 2 RTW

(Code download)

Introduction

This is the second in a series of posts 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 server part of a sample application available for download (the download includes the client 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:

Sample Application Running

The Server

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

PollingDuplexDemo Solution Explorer

There are three projects in the PollingDuplexDemo solution, the first is the Silverlight Application project StockClient.  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 that self-hosts the polling duplex WCF service and represents the server.  You can see from the Visual Studio 2008 solution that there are multiple startup projects, the first to start is the StockServer project along with the StockClientHost project shortly after.

Preliminaries

In order to self-host a WCF service in a Console Application the server needs to register itself as the HTTP handler for a certain URI; this is also termed as registering a portion of the local HTTP namespace.  In order to do this the application needs to run as a local Administrator or the current non-administrative user needs to have been assigned to the ACL for a particular URI, you can read more about this here.  If the server Console Application does not have the appropriate permissions a System.ServiceModel.AddressAccessDeniedException exception is raised with the message "HTTP could not register URL http://+:10201/StockService/. Your process does not have access rights to this namespace."

AddressAccessDeniedException Exception

If you are using Visual Studio 2008 as an Administrator or using Windows XP you won’t run into this problem and I have accounted for it on Windows Vista by creating a UAC manifest (see the app.manifest file) that requires the server to be run as an Administrator.  As a result of this configuration you may encounter the following message the first time you press F5:

Restart using different Credentials

Selecting ‘Restart under different credentials’ re-starts Visual Studio as a local Administrator resulting in the server being able to register and listen at the HTTP address it has been configured with.

The WCF Service Interfaces

The StockServer project references the server-side System.ServiceModel.PollingDuplex.dll assembly and defines one WCF service called StockService.  Here are the ServiceContract interfaces defined in the StockServer namespace:

namespace StockServer
{
    [ServiceContract(Namespace = "Silverlight", CallbackContract = typeof(IStockClient))]
    public interface IStockService
    {
        [OperationContract(IsOneWay = true)]
        void Register(Message message);

        [OperationContract(IsOneWay = true)]
        void Sync(Message message);
    }

    [ServiceContract]
    public interface IStockClient
    {
        [OperationContract(IsOneWay = true, AsyncPattern = true)]
        IAsyncResult BeginReceive(Message message, AsyncCallback callback, object state);

        void EndReceive(IAsyncResult result);
    }

    [ServiceContract]
    public interface IPolicyProvider
    {
        [OperationContract, WebGet(UriTemplate = "/ClientAccessPolicy.xml")]
        Stream ProvidePolicy();
    }
}

Three interfaces are defined in all: IStockService, IStockClient and IPolicyProvider.  IStockService defines the two methods Register and Sync specified as One Way operations, hence the method signatures cannot specify anything other than void as the return type.  Setting IsOneWay to true has the effect that clients will not block waiting for a return message after having called the service as they would with a default request response operation.  A client configured for polling duplex will rather poll asynchronously and pick up messages pushed to them from the service as discussed in part one.  You may also recall these two operations from the value of SOAPAction in the traced HTTP requests in the previous article.  The IStockClient type is specified via the CallbackContract property as the type that will callback to the client from instances of services that implement IStockService.

IStockClient defines the Receive operation as an asynchronous operation by setting AsyncPattern to true and defining Begin and End methods matching the required pattern.  This method is specified as asynchronous to cater for a problem I was encountering when one client aborted or faulted while many other clients were connected.  In this scenario a synchronous method call back to the faulted client resulted in the server thread blocking until the PollTimeout period elapsed for that client with the exception shown in the previous post.  This is fine for the faulted client but the blocking stopped the other connected clients receiving their updates.  The asynchronous version handles the reply using the thread pool so that in the same situation the faulted client will block and timeout on a thread pool thread while the other connected clients still receive their updates as normal.

The IPolicyProvider interface sets up a creative solution (thanks to this post) to the problem of serving up policy requirements to Silverlight 2 clients when using a self-hosted WCF service.  The only change I had to make was to include http-request-headers="*" in the response to ensure compatibility with the Silverlight 2 Beta 2 policy requirements instead of Beta 1.  The declaration above specifies that the ProvidePolicy method should be called in the event of a GET request whose URI includes the relative string specified in UriTemplate.  I have not included support for Flash compatible crossdomain.xml files but this could be easily added using the same method.  WebGet was introduced as part of WCF 3.5, its interesting (though not relevant to Silverlight self-hosted policy requirements) to note that the UriTemplate can also contain curly bracket placeholders that resolve to method parameters for use with REST requests, similar functionality is provided in ASP.NET MVC routing.

The WCF Service Class

The following shows how the previously discussed interfaces are used throughout the declaration of the StockService class.  The StockService class implements only two of the interfaces: IStockService and IPolicyProvider, the usage of the IStockClient interface is shown in the Register method:

namespace StockServer
{
    public sealed class StockService : IStockService, IPolicyProvider
    {
        private StockClient client = null;

        public void Register(Message message)
        {
            client = new StockClient(OperationContext.Current.GetCallbackChannel<IStockClient>(),
                                          "Silverlight/IStockService/Receive");
            client.Faulted += OnClientFaulted;

            InitialReply();

            StockGenerator.Instance.DeltaReceived += OnDeltaReceived;
        }

        public void Sync(Message message)
        {
            Stock delta = message.GetBody<Stock>();

            StockGenerator.Instance.Sync(delta);
        }

        private void InitialReply()
        {
            foreach (Stock stock in StockGenerator.Instance.DemoStocks)
            {
                Reply(stock);
            }
        }

        private void Reply(Stock stock)
        {
            if (client != null)
            {
                client.Send(stock);
            }
        }

        private void OnDeltaReceived(object sender, DeltaReceivedEventArgs e)
        {
            Reply(e.Stock);
        }

        private void OnClientFaulted(object sender, FaultedEventArgs e)
        {
            if (client != null)
            {
                StockGenerator.Instance.DeltaReceived -= OnDeltaReceived;
                client = null;
            }
        }

        public Stream ProvidePolicy()
        {
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";
            return new MemoryStream(File.ReadAllBytes("ClientAccessPolicy.xml"), false);
        }
    }
}

The StockService does not specify a ServiceBehavior attribute as part of it’s declaration so the defaults apply, the most relevant being InstanceContext.PerSession and ConcurrenyMode.Single.  As such, a new instance of the StockService WCF service is created for every client that connects although it is also possible to configure the service as a singleton using the code download with very few changes if that is something you are interested in.

The Register method is called by clients when they first connect to the service, the Message parameter although required by the underlying framework is not used in the method body.  The body firstly creates a new instance of the StockClient class (see code download) passing two parameters to the constructor: a reference to the channel back to the client along with the SOAPAction to use when sending a return message.  Note this SOAPAction string does not mention the IStockClient interface but rather the IStockService interface and contains the name of the synchronous version of the Receive method which is not defined anywhere in user code.  The rest of the method and indeed the class is fairly self-explanatory and centres around subscribing the current instance of StockService to receive deltas from the singleton StockGenerator class (see code download) when a client connects and unsubscribing when a fault occurs attempting to communicate with that client.

The Sync method does make use of the Message parameter sent from the client to extract the Stock (see code download) object and the notes entered against it.  This is again passed on to the singleton StockGenerator class to update it’s state and raise a notification to all other subscribed clients.  The implementation of the ProvidePolicy method simply returns a stream containing the contents of the ClientAccessPolicy.xml file (which is copied to the output directory of the executable) to satisfy the policy requirements required to allow Silverlight clients to connect to this self-hosted service cross-domain.

Hosting the WCF Service

The hosting information for the StockService service is split between the App.config file…

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <diagnostics performanceCounters="All" />
    <services>
      <service name="StockServer.StockService"
               behaviorConfiguration="StockServiceBehaviour">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:10201/StockService" />
          </baseAddresses>
        </host>
        <endpoint address="mex"
                  binding="mexHttpBinding"
                  contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="StockServiceBehaviour">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

… and the StockServiceHost class that inherits from ServiceHost:

namespace StockServer
{
    public class StockServiceHost : ServiceHost
    {
        public StockServiceHost(object singletonInstance, params Uri[] baseAddresses)
            : base(singletonInstance, baseAddresses)
        {
        }

        public StockServiceHost(Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
        }

        protected override void InitializeRuntime()
        {
            Uri baseAddress = this.BaseAddresses[0];
            Uri policyAddress = new Uri(baseAddress.AbsoluteUri.Replace(baseAddress.AbsolutePath, ""));

            this.AddServiceEndpoint(
                typeof(IStockService),
                new PollingDuplexHttpBinding(),
                baseAddress);

            this.AddServiceEndpoint(
                typeof(IPolicyProvider),
                new WebHttpBinding(),
                policyAddress).Behaviors.Add(new WebHttpBehavior());

            base.InitializeRuntime();
        }
    }
}

This is where the server instance of PollingDuplexHttpBinding is instantiated and published as a service endpoint on the base address retrieved from the configuration file.  Two other endpoints are defined for this service, one is the Metadata Exchange endpoint which you can navigate to in a browser when the server is running; the other is the endpoint for the policy response bound to the root of the base address.  The constructors included simply defer to the base constructors of ServiceHost, the first one allows this class to be optionally used as a host of a singleton polling duplex service instance.  When the Console Application starts it simply creates an instance of the StockServiceHost class and opens the communication channel to accept clients:

namespace StockServer
{
    class Program
    {
        static void Main(string[] args)
        {
            // Set Console display info
            Console.Title = "Polling Duplex Stock Service";
            Console.ForegroundColor = ConsoleColor.Cyan;

            using (StockServiceHost serviceHost = new StockServiceHost(typeof(StockService)))
            {
                serviceHost.Open();

                Console.WriteLine("Polling Duplex Stock Service Started...");
                Console.WriteLine("Press <ENTER> to terminate service.");
                Console.ReadLine();
            }
        }
    }
}

The same stock prices are sent to all client channels at approximately the same time in each StockService instance’s handler for the singleton StockGenerator class’s DeltaReceived event via the StockClient instance.  The full code for the definitions of the StockGenerator and StockClient classes is available in the code download.  Currently new deltas are generated every 250.0 milliseconds using the ThreadPool, obviously the more this interval is reduced the more noticeable the performance degradation; especially below 100.0 milliseconds on my machine with two clients in separate browsers or tabs things eventually get out of sync.  Comparing this solution to a similar solution I’ve published using Sockets which can keep clients in sync at 1.0 millisecond intervals it’s clear to see there is work to be done on performance and it’s good to see the Silverlight Webservices Team acknowledge this themselves.

Posted in Silverlight, WCF | Tagged: , , , , , | 18 Comments »

Silverlight 2 WCF Polling Duplex Support – Part 1: Architecture

Posted by petermcg on September 3, 2008

Edited on 20/October/2008 : Code download updated to support Silverlight 2 RTW

(Code download)

Introduction

This is the first in a series of posts 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 take a look at the general architecture of the technology and it’s approach to solving the problem of bi-directional communication between a server and a browser.  Parts two and three introduce the code for a client and server sample application that demonstrates the technology in practice.

The Assemblies

Support for bi-directional communication between a WCF Service and a Silverlight 2 Client over HTTP (port 80 by default) was introduced as part of the Silverlight 2 Beta 2 SDK. This is different to the Sockets infrastructure also supported by Silverlight 2 that requires a custom port within a specific range to be opened.  Two assemblies both named System.ServiceModel.PollingDuplex.dll provide this duplex support, one for the client, one for the server.  It’s clear to see which is which from the location each assembly is extracted to by the SDK installer:

%ProgramFiles%\Microsoft SDKs\Silverlight\v2.0\Libraries\Client\System.ServiceModel.PollingDuplex.dll

%ProgramFiles%\Microsoft SDKs\Silverlight\v2.0\Libraries\Server\Evaluation\System.ServiceModel.PollingDuplex.dll

Location aside, it’s possible to discern between the two based on the public key token of mscorlib.dll the assembly was built against (thanks to this post).  The client assembly is built against the Silverlight version, the server assembly against the .NET Framework version:

Client Assembly Manifest

Server Assembly Manifest

Both assemblies host types in two main namespaces familiar to WCF developers: System.ServiceModel and System.ServiceModel.Channels.  The content of the two assemblies in terms of types in these namespaces is similar but not identical.  The server side assembly has around nine additional internal types (PartialTrustHelpers, ContextQueue, IOThreadTimer, PollingDuplexChannelListener, RequestContextData, SafeNativeMethods, ServicePollingDuplexDispatcher, ServicePollingDuplexSessionChannel and UnsafeNativeMethods) with names that suggest they provide functionality outside of what can be achieved in the client sandbox.  The server assembly is therefore slightly larger than the client (101KB vs. 81KB) but from an API perspective they are identical in that each exposes only two public types: System.ServiceModel.PollingDuplexHttpBinding and System.ServiceModel.Channels.PollingDuplexBindingElement.  A little more exploration into these two types reveals that an instance of the latter is encapsulated by the former.  By this simple process of elimination it’s clear to see that creating an instance of the PollingDuplexHttpBinding type from both the client and server assemblies is the starting point to enabling WCF duplex communication in Silverlight 2 Beta 2.

The Binding

There are many different types of WCF Bindings, you can think of each one as a unique package of decisions with regard to the transport, message encoding, security and protocol details that will be employed to transfer a communication on the wire between a client and server or vice versa.  With these decisions encapsulated by the binding, enabling congruent communication between the client and server is simply a matter of instantiating the same binding type on both sides.  The PollingDuplexHttpBinding binding inherits from the standard WCF BasicHttpBinding class and specializes it to produce a new WCF binding specifically designed for communication with a Silverlight 2 client application.  The decisions packaged in the binding by default are to use HTTP as the transport, UTF-8 text as the message encoding, BasicHttpSecurityMode.None as the security setting (configurable in the constructor) and in addition to HTTP, Net Duplex and WS-Make Connection (WS-MC) as the protocols.  These protocols in turn rely on the XML and SOAP specifications, the Net Duplex Protocol is implemented to establish the Silverlight 2 client session with the server and WS-MC is used to establish a back channel using polling so that server initiated messages can be delivered to the client.  These protocols are specifically designed to be used in place of more traditional duplex protocols like TCP and cater for the fact that a Silverlight application running in a browser cannot accept new incoming connections unless a custom port is opened and agreed upon.  Another way to look at this is that without opening a custom port a Silverlight client application is not addressable by protocols such as TCP by default because the browser environment does not allow the creation of a listener. Consequently, with polling duplex communication the server must be addressable and the Silverlight application must initiate the connection to it.  In order to create the illusion of duplex communication (where either party can initiate a message exchange) in such a scenario, Microsoft have chosen to have the client poll the server for return messages after initiating the connection.

Polling and Timeouts

Once connected and until a fault or timeout occurs the client asynchronously polls the server more or less constantly give or take a few milliseconds between poll timeouts.  A poll returns when either there is a message to convey to the client or a poll-timeout occurs.  The default poll-timeout is currently around 60,000 milliseconds (60 seconds rather than 90 as you may have read elsewhere) but this can be altered by setting the PollTimeout property of an instance of PollingDuplexHttpBinding which will be forwarded to the identically named property of the binding’s encapsulated instance of PollingDuplexBindingElement.  When a PollTimeout occurs an exception is not raised unless either the client or server only has aborted the session during that time in which case the following exception is raised:

PollTimeout Exception

When the session has not been aborted and is still active, the occurrence of a PollTimeout does not raise an exception, an empty response (HTTP/1.1 200 OK with Content-Length:  0) is returned and a new poll originates on the network layer.  In the few milliseconds after a poll timeout occurs and before a new poll occurs, a queue of any messages to be sent to the client is maintained on the server ready to be flushed on the next poll.  There is another type of timeout that does raise an exception every time it occurs.  InactivityTimeout by default is set to 10 minutes (again configurable via the binding) and so will occur after 10 poll timeouts in a sequence, that is – after a period of 10 minutes during which no bytes have been sent or received by either party.  The following image shows the exception raised after an InactivityTimeout:

InActivityTimeout Exception

Anatomy of a Bi-Directional Message Exchange

In order to illustrate what I’ve covered so far and progress further a familiar context needs to be established in the form of a sample application.  The second and third posts in this series introduce the server and client parts of the sample application respectively and I encourage you to download the Visual Studio 2008 Solution, familiarize yourself with how it works and have a look at the code and comments.  The solution 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:

Sample Application Running  

An excerpt of the most significant bi-directional conversation between one client and server assuming no communication problems would include the following events:

A. Client Connects To Server
B: Server Send Client An Empty Response
C. Client Polls Server For A Response
D. Server Sends Response Per Polling Client
E. Client Sends User Notes To Server
F. Server Sends Response Per Polling Client Including Notes

Tracing these events on the wire in practice results in the following traffic:

A. Client Connects To Server
POST /StockService HTTP/1.1
Accept: */*
Content-Length: 394
Content-Type: text/xml; charset=utf-8
SOAPAction: "Silverlight/IStockService/Register"
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; InfoPath.2; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618)
Host: 127.0.0.1:10201
Connection: Keep-Alive
Pragma: no-cache

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <netdx:Duplex xmlns:netdx="http://schemas.microsoft.com/2008/04/netduplex">
      <netdx:Address>http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=d640645c-4650-4889-8fd0-e7bf52156bcc</netdx:Address>
      <netdx:SessionId>e694f6c9-bab4-46a4-b81c-e5488609b48d</netdx:SessionId>
    </netdx:Duplex>
  </s:Header>
  <s:Body />
</s:Envelope>

This is a HTTP POST of the Net Duplex request from the client to the server to establish a unique session.  Note the presence of Connection: Keep-Alive and the SOAPAction in the HTTP request specifying the WCF service method to call (see part two for the server code).  The Net Duplex information is present as part of the SOAP header and includes two separate GUID identifiers in the Address and Session elements.  The Address element appears to specify an identifier for use by WS-MC (note the URI part of the identifier is the WS-MC anonymous URI although anonymous is spelt incorrectly) and the SessionId element appears to uniquely identify the current Net Duplex session; the SOAP body is empty.

B: Server Send Client An Empty Response
HTTP/1.1 200 OK
Content-Length: 0
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 01 Sep 2008 11:27:43 GMT

The server sends back an empty response (Content-Length: 0) indicating that the connection was successful but no messages destined for this unique session’s client are available at this time.

C. Client Polls Server For A Response
POST /StockService HTTP/1.1
Accept: */*
Content-Length: 318
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://docs.oasis-open.org/ws-rx/wsmc/200702/MakeConnection"
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; InfoPath.2; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618)
Host: 127.0.0.1:10201
Connection: Keep-Alive
Pragma: no-cache

<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=d640645c-4650-4889-8fd0-e7bf52156bcc</wsmc:Address>
    </wsmc:MakeConnection>
  </s:Body>
</s:Envelope>

The above is a second HTTP POST to the server from the client a few milliseconds after the initial Net Duplex request.  This time the SOAPAction identifies it as a WS-MC MakeConnection request.  The SOAP envelope has no header but the body contains the same URI and GIUD identifier established in the Address element of the previous Net Duplex request.

D. Server Sends Response Per Polling Client
HTTP/1.1 200 OK
Content-Length: 578
Content-Type: text/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 01 Sep 2008 11:27:45 GMT

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <netdx:Duplex xmlns:netdx="http://schemas.microsoft.com/2008/04/netduplex">
      <netdx:Address>http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=d640645c-4650-4889-8fd0-e7bf52156bcc</netdx:Address>
      <netdx:SessionId>e694f6c9-bab4-46a4-b81c-e5488609b48d</netdx:SessionId>
    </netdx:Duplex>
  </s:Header>
  <s:Body>
    <Stock xmlns="urn:petermcg.wordpress.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <Ask>256.7</Ask>
      <Bid>256.02</Bid>
      <Notes i:nil="true"/>
      <Symbol>AAHH</Symbol>
    </Stock>
  </s:Body>
</s:Envelope>

The above is the first real response sent back along the HTTP response channel or "back-channel" (a channel capable of carrying a SOAP message, without initiating a new connection) in reaction to the client’s connection request and polling.  This time the server has queued a message destined for this unique session’s client in the form of a serialized stock object with the symbol AAHH.  The content-length is therefore greater than zero this time as the response consists of a SOAP envelope with the Net Duplex information in the header and the serialized Stock object in the body. See the server code in part two for how the Stock objects are serialized using the DataContractSerializer.

E. Client Sends User Notes To Server
POST /StockService HTTP/1.1
Accept: */*
Content-Length: 607
Content-Type: text/xml; charset=utf-8
SOAPAction: "Silverlight/IStockService/Sync"
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; InfoPath.2; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618)
Host: 127.0.0.1:10201
Connection: Keep-Alive
Pragma: no-cache

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <netdx:Duplex xmlns:netdx="http://schemas.microsoft.com/2008/04/netduplex">
      <netdx:Address>http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=d640645c-4650-4889-8fd0-e7bf52156bcc</netdx:Address>
      <netdx:SessionId>e694f6c9-bab4-46a4-b81c-e5488609b48d</netdx:SessionId>
    </netdx:Duplex>
  </s:Header>
  <s:Body>
    <Stock xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:petermcg.wordpress.com">
      <Ask>227.69</Ask>
      <Bid>226.74</Bid>
      <Notes>Notes added from Internet Explorer</Notes>
      <Symbol>CZLP</Symbol>
    </Stock>
  </s:Body>
</s:Envelope>

This time we see a similar SOAP envelope to the previous response except travelling in the opposite direction.  The SOAPAction in the HTTP POST request is specifying that the Sync method of the WCF service should be called (again see part two for the server code).  The SOAP envelope again contains the identifying Net Duplex information in the header and the body contains a single serialized stock instance, this time with some notes entered by the user.

F. Server Sends Response Per Polling Client Including Notes
HTTP/1.1 200 OK
Content-Length: 607
Content-Type: text/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 01 Sep 2008 11:28:17 GMT

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <netdx:Duplex xmlns:netdx="http://schemas.microsoft.com/2008/04/netduplex">
      <netdx:Address>http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=d640645c-4650-4889-8fd0-e7bf52156bcc</netdx:Address>
      <netdx:SessionId>e694f6c9-bab4-46a4-b81c-e5488609b48d</netdx:SessionId>
    </netdx:Duplex>
  </s:Header>
  <s:Body>
    <Stock xmlns="urn:petermcg.wordpress.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <Ask>227.69</Ask>
      <Bid>226.74</Bid>
      <Notes>Notes added from Internet Explorer</Notes>
      <Symbol>CZLP</Symbol>
    </Stock>
  </s:Body>
</s:Envelope>

On receipt of the notes post from the client, if there are no messages pending the server sends back an empty response as shown earlier.  A few milliseconds later when a client polls again or if there are other clients polling  as shown previously, the received update is echoed back in the response above.  To see the entire client and server code involved in generating these transmissions, download the sample Visual Studio 2008 solution attached to parts two or three.

Comet

Like all good problems, server to browser communication has prompted many slightly different solution implementations over the years.  The term Comet was first defined here (the technologies covered by the umbrella existed long before) as an umbrella term used to discuss these different implementations as a group when necessary (although it seems at one point it was nearly used as the codename for ASP.NET AJAX instead of Atlas).  More commonly the term is used in context with browser-native technologies such as JavaScript but I have seen it used to include proprietary plugins such as Silverlight.  The browser-native implementations apply methods of maintaining long lived connections between a browser and a server commonly using streaming or long-polling to enable partial page updates from JavaScript based on server initiated events.  If done correctly, this can result in near real time updates to a web-page without it ever explicitly requesting those updates.  Just some examples of applications that already use this kind of technology include:

Google Talk

Google Maps

Lightstreamer

Liberator

Kaazing

BlazeDS

Orbiter

Historically, browser-native Comet implementations have had to overcome several obstacles often based around the fact that the underlying technologies were not specifically designed to work in the way they were being manipulated.  Examples include browser compatibility, firewalls terminating long running connections and problems with incorrectly configured proxies.  Another obstacle is that keeping a long-lived connection open to a particular server uses up one of the simultaneous connections allowed by the HTTP 1.1 specification (have a look at the sixth paragraph under heading 8.1.4 here).  This can lead to browser usability problems but is often overcome by using a sub-domain on the same server as the initiator of the update events only.  It is very interesting to note that there are some changes around this restriction due in Internet Explorer 8 for broadband connections.  From a Silverlight perspective, as the implementation runs inside a plug-in the cross-browser compatibility obstacle is alleviated as long as the user has the plug-in installed.  These obstacles and their many different solutions by the different implementations of Comet in use today imply that no one solution is yet perfect across all browsers in all scenarios.  Work is underway to attempt to do something about this by standardising native support for server-sent events, event streaming and Web sockets in the HTML 5 specification, the latest Editor’s Draft at the time of writing is as up-to-date as a few days ago on 29 August 2008.  Native browser support for such communication in the future prompts musings on whether Microsoft could add not only polling but streaming duplex support to ASP.NET applications in the future using SOAP/JSON, the ASP.NET AJAX client framework, and an ASP.NET server control or WCF; and whether the messages would be compatible with this Silverlight framework on the wire and over the HTML bridge.

Posted in Comet, PollingDuplex, Silverlight, WCF | Tagged: , , , , , , , , , | 13 Comments »

Silverlight Initialization via the ASP.NET Silverlight Server Control

Posted by petermcg on July 16, 2008

Edited on 20/October/2008 : Code download updated to support Silverlight 2 RTW

(Code download)

This post takes a look at the ASP.NET Silverlight server control and what it renders as markup in response to a web request when added to an ASP.NET page.  It then digs deeper to explore how this markup describes the Silverlight plug-in and how this rendered plug-in is itself initialized in the Microsoft ASP.NET AJAX client framework.  This post also shows how to specify and retrieve Silverlight initialization parameters when using the server control.

The ASP.NET Silverlight Server Control

The Silverlight control is one of two ASP.NET server controls included in the Silverlight 2 Beta 2 SDK; the other is the MediaPlayer control which actually inherits from the Silverlight control.  The control provides an easy way to include Silverlight content in a new or existing ASP.NET Web page using the familiar ASP.NET server control model.  The control is available in the System.Web.Silverlight.dll assembly located under %ProgramFiles%\Microsoft SDKs\Silverlight\v2.0\Libraries\Server in the System.Web.UI.SilverlightControls namespace.  The Silverlight content to be executed by the plug-in this control ultimately renders can be specified by setting the value of the Source property to a URI either to a XAML file or in Silverlight 2 a compiled Silverlight application (a .xap file):

<asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/InitParameters.xap"
                 MinimumVersion="2.0.30523" Width="100%" Height="100%" />

The Silverlight server control requires access to a ScriptManager control (generally on the same page) otherwise an InvalidOperationException will be thrown at runtime when the control initializes.

Rendered Markup and the ASP.NET AJAX Client Framework

When rendered to the output stream as part of an ASP.NET page it is declared in, the Silverlight server control delivers four additions to the markup eventually interpreted by the browser.  The first addition the control renders is a JavaScript script block under the html <body> tag with it’s src property set to a path that includes the string "ScriptResource.axd":

<script src="/InitParametersWeb/ScriptResource.axd?d=wMEjThkBoZWGT...;t=633481889860000000"
        type="text/javascript"></script>

This resource is requested by the Silverlight server control in it’s implementation of the IScriptControl interface.  When this script block is encountered as the html page loads a GET request is made to resolve the path assigned to the src property; because the path includes the string "ScriptResource.axd", the request is handled by an instance of the ScriptResourceHandler class as mapped in the default web.config file under the httpHandlers element:

<httpHandlers>
    <add verb="GET,HEAD"
       path="ScriptResource.axd"
       type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0,
             Culture=neutral, PublicKeyToken=31BF3856AD364E35"
       validate="false"/>
</httpHandlers>

The ScriptResourceHandler class implements the IHttpHandler interface in a way that handles request processing for script files that are embedded as resources in an assembly.  In this case the handler eventually resolves the path to a gzip compressed version of either the SilverlightControl.js or SilverlightControl.debug.js JavaScript file (depending on the ScriptMode property of the required ScriptManager control and in turn on the compilation mode in web.config) embedded in the System.Web.Silverlight.dll assembly:

System.Web.Silverlight Resources

It’s the JavaScript code in this file that (amongst other things) registers the Sys.UI.Silverlight ASP.NET AJAX client namespace, the definition of the Sys.UI.Silverlight.Control client component class (with Sys.UI.Control as the base class) and the definition of a method called createObject.

The second addition to the markup is a html <span> element:

<span id="Xaml1_parent"></span>

This <span> element is referenced in the third piece of markup the Silverlight server control renders. Another JavaScript script block, this time with some in-line code that calls the previously defined createObject method:

<script type="text/javascript">

//<![CDATA[

Sys.UI.Silverlight.Control.createObject('Xaml1_parent', '<object 
type="application/x-silverlight-2-b2" data="data:application/x-silverlight-
2-b2," id="Xaml1" style="height:100%;width:100%;">\r\n\t<param
name="MinRuntimeVersion" value="2.0.30523">\r\n\r\n\t</param><a
href="http://go2.microsoft.com/fwlink/?LinkID=114576&amp;v=2.0"><img
src="http://go2.microsoft.com/fwlink/?LinkID=108181" alt="Get Microsoft
Silverlight" style="border-width:0;" /></a>\r\n</object>'
); //]]> </script>

The createObject method takes two parameters here, the first is a string specifying the id of a parent element (the <span>) the second parameter is a string containing html markup for the Silverlight plug-in using the <object> element. The body of the createObject method finds the parent element on the page using the id in the first parameter and sets it’s innerHtml property to be the second parameter. When the innerHTML property is set, the <object> element becomes the content of the <span> element resulting in the object tag describing the Silverlight plug-in becoming part of the browser’s DOM.

The fourth and final markup snippet rendered by the ASP.NET Silverlight server control is the in-line JavaScript script statement shown below:

Sys.Application.add_init(function() {
    $create(Sys.UI.Silverlight.Control, {"source":"ClientBin/InitParameters.xap"},
            null, null, $get("Xaml1_parent"));
});

This script statement adds an anonymous JavaScript function as an event handler for the init event of the Sys.Application client object defined by ASP.NET AJAX. Handlers for this init event are executed after the DHTML window object’s onload event and after the ASP.NET AJAX runtime has been initialized.  The body of this anonymous function contains a single statement that invokes the $create alias defined by the Microsoft ASP.NET AJAX library in the MicrosoftAjax.js or MicrosoftAjax.debug.js JavaScript file embedded in the System.Web.Extensions.dll assembly.

The $create alias is just an convenience for calling the Sys.Component.create function which performs a number of steps.  It creates an instance of the Sys.UI.Silverlight.Control client class within the ASP.NET AJAX client framework as a visual component associated with the span element (this association is required for visual controls) returned as the last parameter from the call to the $get alias. Crucially it sets the value of the ‘source’ property on this component instance to the .xap file using the key/value object literal generated from the server control ‘Source’ property and passed as the second parameter.  It calls the initialize method inherited from the base Sys.UI.Control class on the component instance and finally stores a reference to the instance as a child component of the global Sys.Application object via it’s addComponent method.

At this stage I believe the Silverlight-plug-in, now part of the browser DOM and registered as a visual component in the Microsoft ASP.NET AJAX client framework with it’s source property set to the .xap file, performs the following steps outlined in the Silverlight 2 Beta 2 documentation:

"…starts the common language runtime (CLR) and creates an application domain for the Silverlight application to run in. Downloads the application package, works with the Silverlight runtime to create an execution environment for the Silverlight application, and loads the assemblies and resource files included in the application package. The next step is to instantiate the application class to start the application running."

The Silverlight Application Class

Several of Microsoft’s API’s follow a similar pattern of having an instance of an Application type as the entry point into the program. The client-side ASP.NET AJAX framework, the ASP.NET server-side framework, WPF and Silverlight 2 all employ this pattern. The Application class is the one class that every Silverlight application must implement.  When you create a ‘Silverlight Application’ using the default Visual Studio 2008 template, the Application class is inherited by the ‘App’ class defined in the App.xaml and App.xaml.cs files.  It’s the instance of this App class that initializes and starts the main UI of the Silverlight Application by creating an instance of the Page class (defined in Page.xaml and Page.xaml.cs) and assigning it to the RootVisual property.

Initialization Parameters

To specify initialization parameters to Silverlight from a basic Html page the options are to either set the initParams parameter to a correctly formatted string using JavaScript against the Silverlight plug-in’s object model, or to set the value of a <param> element named InitParams to the same formatted string under the Silverlight plug-in’s <object> tag using Html.  The formatted string should match the pattern : "key1=value1,key2=value2,key3=value3".

The same functionality can be achieved when hosting Silverlight using the ASP.NET Silverlight server control by setting the server-side InitParameters property to the exact same formatted string.  The InitParameters server control property is of type string, is stored in ViewState, and can be set in markup and code-behind; it’s important that this string is in the expected format to ensure the parameters make it to Silverlight.  When a Silverlight server control with it’s InitParameters property set ultimately renders itself, the string is appropriately escaped and passed on to become the value of a <param> element named InitParams inside the generated Silverlight plug-in <object> element.

The key/value pairs in this formatted string are parsed into an IDictionary<string, string> and are able to be retrieved in Silverlight 2 in the StartupEventArgs.InitParams property (e.InitParams) in an event handler for the Application.Startup event:

Application Startup with Parameters

The sample visual studio solution available for download above demonstrates how to specify initialization parameters in both markup and code behind on a Silverlight ASP.NET server control and how to retrieve those parameters in an event handler for the Silverlight Application.Startup event.  The sample also demonstrates passing the parameters on to an instance of the Page class to be displayed in a DataGrid.

Posted in ASP.NET, Silverlight | Tagged: , , , , , , , , | 14 Comments »

Stock List Demo Optimised

Posted by petermcg on July 4, 2008

Edited on 20/October/2008 : Code download updated to support Silverlight 2 RTW

(Code download)

I’ve recently addressed some comments on the blog and also optimised the Stock List Demo sample application, particularly the serialization.  The updates or deltas can now be pushed to clients at intervals from approx 1 millisecond.  If you haven’t seen this demo or haven’t downloaded it in a while it’s worth taking a look at the latest version.  Here is a screenshot of the server and one client application running :

Application Running

The sample comprises a Silverlight application with a DataGrid bound to an ObservableCollection<T> initially populated from a call to a Wcf Service and then updated by deltas received over Tcp Sockets.  The sample as a whole provides code that demonstrates some interesting different areas of Silverlight 2 development.  Silverlight Sockets support is used to push updates to one or many connected clients now at intervals from approx 1 millisecond upwards (the sample defaults to 50 milliseconds but feel free to change this to speed the sample up or slow it down) :

// Server
private void ServeDeltas()
{
    while (true)
    {
        if (synchClients.Count > 0)
        {
            // Send latest delta to all subscribed clients
            synchClients.SendAll(GetLatestDelta());

            // Set this to anything from 1.0 millisecond
            Thread.Sleep(TimeSpan.FromMilliseconds(50.0));
        }
        else
        {
            clientEvent.WaitOne();
        }
    }
}

The Silverlight UI remains responsive and notes can still be added against stocks in the DataGrid at these speeds.  The serialization has been optimised to use the DataContractSerializer and MemoryStream classes to more efficiently and reliably serialize and deserialize Stock objects from server to client :

// Server
public void Send(Stock stock)
{
    byte[] bytes = new byte[1024];

    using (MemoryStream memStream = new MemoryStream(bytes))
    {
        xmlSerializer.WriteObject(memStream, stock);

        try
        {
            memStream.WriteTo(netStream);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }
}

// Client
private void OnSocketAsyncEventCompleted(object sender, SocketAsyncEventArgs e)
{
    Socket socket = e.UserToken as Socket;
    byte[] buffer = new byte[1024];

    switch (e.LastOperation)
    {
        case SocketAsyncOperation.Connect:
            // Set up receive buffer
            e.SetBuffer(buffer, 0, buffer.Length);
            // Asynchronously request to receive data from the server
            socket.ReceiveAsync(e);
            break;
        case SocketAsyncOperation.None:
            break;
        case SocketAsyncOperation.Receive:
            // Deal with received data from the server in our buffer
            int bytesTransferred = e.Buffer.TakeWhile(b => b != byte.MinValue).ToArray().Length;

            using (MemoryStream memStream = new MemoryStream(e.Buffer, 0, bytesTransferred, false))
            {
                try
                {
                    Stock stock = xmlSerializer.ReadObject(memStream) as Stock;

                    Action<Stock> action = UpdateStockList;
                    owner.BeginInvoke(action, stock);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                }
            }

            // Clear receive buffer
            e.SetBuffer(buffer, 0, buffer.Length);

            // Asynchronously request to receive more data from the server
            socket.ReceiveAsync(e);
            break;
        case SocketAsyncOperation.Send:
            break;
        default:
            break;
    }
}

The sample also shows how to use the Silverlight Dispatcher and Action<T> to queue work for execution on the UI thread (see client code after ReadObject above) and also how to apply an Action<T> to each element in a List<T> using ForEach :

// Client
private void OnGetStocksCompleted(object sender, GetStocksCompletedEventArgs e)
{
    Action<Stock> action = delegate(Stock delta)
    {
        StockDecorator stock = stockList.FirstOrDefault(s => s.Symbol == delta.Symbol);

        if (stock == default(StockDecorator))
        {
            stockList.Add(new StockDecorator(delta));
        }
    };

    e.Result.ForEach(action);
}

The Silverlight Socket policy requirements are handled on a separate worker thread in the same server that sends the Stock updates :

// Server
private void ServePolicy()
{
    while (true)
    {
        Console.WriteLine("Accepting Policy Requests...");

        // Read policy file
        byte[] policyBytes = File.ReadAllBytes("PolicyResponse.xml");

        using (Socket client = policyListener.AcceptSocket())
        {
            Console.WriteLine("Policy Request Accepted...");

            // Get poilicy request header
            byte[] buffer = new byte[1024];
            int bytesReceived = client.Receive(buffer);

            // Basic check of request header
            string header = Encoding.UTF8.GetString(buffer, 0, bytesReceived);

            if (header == "<policy-file-request/>")
            {
                client.Send(policyBytes, 0, policyBytes.Length, SocketFlags.None);
            }
        }
    }
}

The animation of the Stock Updates (green for positive, red for negative) is created programmatically and the sample also shows how to bind to those animations from Xaml :

<data:DataGrid x:Name="StocksGrid"
               AutoGenerateColumns="False"
               Grid.Row="1"
               Grid.ColumnSpan="2"
               GridlinesVisibility="Horizontal"
               HeadersVisibility="Column"
               RowBackground="AliceBlue"
               IsReadOnly="True"
               CanUserResizeColumns="False"
               Margin="0,5,0,0">
    <data:DataGrid.Columns>
        <data:DataGridTemplateColumn Header="Symbol">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Symbol}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn Header="Bid">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Background="{Binding BidHighlight}">
                        <TextBlock Text="{Binding Bid}" />
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn Header="Ask">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Background="{Binding AskHighlight}">
                        <TextBlock Text="{Binding Ask}" />
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn MinWidth="150" Header="Notes">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Notes}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
    </data:DataGrid.Columns>
</data:DataGrid>

Multiple clients from different browsers can be connected by firing up a new browser and copy/pasting the address, they will receive the same updates (notice the highlights in the screenshot below) :

Multiple Clients Connected

The server will pause using an AutoResetEvent if there are no clients connected and resume when at least one client connects (you can also pause the server by pressing the pause key on your keyboard and resume by pressing Enter).  If you are working on a proof on concept for Silverlight 2 hopefully this is an example of a substantial application which may be helpful.

Posted in Silverlight, Sockets, WCF, XAML | Tagged: , , , , , | 12 Comments »

Trivia : Argentum

Posted by petermcg on June 16, 2008

You may have seen the Ag prefix used in various contexts with Silverlight such as in the naming of agcore.dll, and the Upcoming AgDataGrid Suite for Silverlight 2 Beta 2.  The reason Ag is used is that it’s the symbol in the periodic table for the transition metal Silver, originally from the Latin Argentum 🙂

Posted in Silverlight | Tagged: , , | Comments Off on Trivia : Argentum

Silverlight 2.0 Stock List Demo – Part 3

Posted by petermcg on June 5, 2008

Edited on 10/June/2008 : Code download updated to Silverlight 2 Beta 2

Edited on 20/October/2008 : Code download updated to support Silverlight 2 RTW

Part Three : Making the Grid Tick with Server Deltas via Sockets – (Code download)

In Part 2 we had a DataGrid control being populated with 1,000 stocks via a ‘state of the world’ response from a call to our WcfStockService web service. To progress with our sample financial stock ticker application the next area to look at is updating the bid and ask prices, to do this we could call the web-service at a specified interval, but this is not ideal. What we really want is not to have to ‘pull’ updates from the server but have the server ‘push’ updates to listening clients.

More advanced duplex communication and ‘push’ support is possibly arriving in the imminent release of Silverlight 2 Beta 2; eventually along with the full cross-domain model for Sockets but in the interim there is basic support for duplex connectivity with the application’s host of origin in Silverlight 2 Beta 1 provided by the System.Net.Sockets.Socket class. This article shows how to use this networking support in Silverlight 2 Beta 1 to send updates and additions (deltas) to one or many Stock List clients from an implementation of a Socket based Stock delta server.

Here’s what our Visual Studio 2008 solution looks like for Part 3 :

Solution Explorer Part 3

The most notable changes in this image since Part 2 are the addition of the new StockDeltaService Console Application project and the StockDecorator class in the Silverlight Application project.

The Server

The new StockDeltaService Console Application project contains the implementation of the Stock delta server. Currently, the only supported protocol for Socket data transfer in Silverlight 2.0 is the TCP protocol so the server uses the standard TcpListener class to listen for clients on an agreed endpoint. Once accepted, a reference to the communication stream back to the client is stored in a thread-safe collection of type SynchronizedCollection<StockClient> :

public void Start()
{
    // Start worker thread
    Thread workerThread = new Thread(ServeDeltas) { IsBackground = true };
    workerThread.Start();

    // Listen for clients
    listener.Start();
    Console.WriteLine("Server started...");

    while (true)
    {
        Console.WriteLine("Accepting clients...");

        // Main Thread's only duty is to block here listening for clients
        StockClient client = new StockClient(listener.AcceptTcpClient());

        Console.WriteLine("Client accepted...");

        // Don't block the Main Thread in Add
        ThreadPool.QueueUserWorkItem(o => synchClients.Add(o as StockClient), client);
    }
}

The main server thread’s only duty is to listen for and accept connections from new clients, a manual background thread does the actual work of generating sample prices and sending these deltas to the previously registered connected clients. The server has a reference to the same WCF web service used in Part 2 and it is the simple Stock type that is sent one at a time in serialized form to the connected clients in the collection. Each Stock object is serialized on the server and eventually deserialized on the client using the XmlSerializer class, the same class that ASP.NET uses to encode XML Web service messages.

By default the server in the code download sample sends deltas every 50 milliseconds, I have had the code send deltas for 1,000 stocks to multiple clients every 5 milliseconds which works fine on my computer in Release mode (Start Without Debugging). Download the code above for more detailed information.

The Client

On the client side, in order to facilitate communication via Sockets with the server the StockTicker class now exposes the SubscribeDeltas() method :

public void SubscribeDeltas()
{
    // Creates a Socket that can be used to communicate on a TCP/IP-based network
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    // NOTE : It is advantageous to retain the reference to the SocketAsyncEventArgs object
    SocketAsyncEventArgs args = new SocketAsyncEventArgs();
    args.UserToken = socket;
    args.RemoteEndPoint = new DnsEndPoint(Application.Current.Host.Source.DnsSafeHost, 4502);
    args.Completed += OnSocketAsyncEventCompleted;

    // Asynchronously connect to server and process response in OnSocketAsyncEventCompleted
    socket.ConnectAsync(args);
}

private void OnSocketAsyncEventCompleted(object sender, SocketAsyncEventArgs e)
{
    Socket socket = e.UserToken as Socket;

    switch (e.LastOperation)
    {
        case SocketAsyncOperation.Connect:
            // Set up receive buffer
            byte[] buffer = new byte[1024];
            e.SetBuffer(buffer, 0, buffer.Length);
            // Asynchronously request to receive data from the server
            socket.ReceiveAsync(e);
            break;
        case SocketAsyncOperation.None:
            break;
        case SocketAsyncOperation.Receive:
            // Deal with received data from the server in our buffer
            string decoded = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);

            using (StringReader sreader = new StringReader(decoded))
            {
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(Stock));

                try
                {
                    // Deserialize single Stock object
                    Stock stock = xmlSerializer.Deserialize(sreader) as Stock;

                    if (stock != null)
                    {
                        // Queue call to UpdateStockList method passing delta parameter on the thread that created StockTicker
                        Action<Stock> action = UpdateStockList;
                        owner.Dispatcher.BeginInvoke(action, stock);
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                }
            }

            // Asynchronously request to receive more data from the server
            socket.ReceiveAsync(e);
            break;
        case SocketAsyncOperation.Send:
            break;
        default:
            break;
    }
}

This public method is called on the same instance of the StockTicker from Part 2 in the constructor of Page.xaml.cs just below our previous call for the state of the world data. The SubscribeDeltas method firstly creates a Socket that can be used to communicate on a TCP/IP-based network (such as the Internet) and uses it along with a new instance of the SocketAsyncEventArgs class to start an asynchronous connection request to the server with the Completed event handled by the OnSocketAsyncEventCompleted method. Note that this sample uses port 4502, one restriction in Silverlight 2 Beta 1 is that the port range that a network application is allowed to connect to must be within the range 4502-4532.

Once connected with the server, the client waits to asynchronously receive the updates it has subscribed to. When deltas are received the Stock object is deserialized and the StockList collection is updated via the UpdateStockList method on the thread that created the StockTicker class. Queuing the call to UpdateStockList for execution on the creating thread using Dispatcher.BeginInvoke is necessary to prevent an invalid cross-thread access exception when a collection (StockList in this case) is updated from a different thread than the one that created it.

Changes to the client presentation in Page.xaml are minimal and are focused around binding to the highlighting animation that takes place when a price for a Stock changes :

<data:DataGrid x:Name="StocksGrid"
    Grid.Row="1"
    Grid.ColumnSpan="2"
    GridlinesVisibility="Horizontal"
    HeadersVisibility="Column"
    RowBackground="AliceBlue"
    IsReadOnly="True"
    CanUserResizeColumns="False"
    Margin="0,5,0,0">
    <data:DataGrid.Columns>
        <data:DataGridTemplateColumn Header="Symbol">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Symbol}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn Header="Bid">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Background="{Binding BidHighlight}">
                        <TextBlock Text="{Binding Bid}" />
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn Header="Ask">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Background="{Binding AskHighlight}">
                        <TextBlock Text="{Binding Ask}" />
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn MinWidth="150" Header="Notes">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Notes}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
    </data:DataGrid.Columns>
</data:DataGrid>

A StackPanel now wraps the display of each price with it’s background color bound to the appropriate System.Windows.Media.Brush exposed by the new StockDecorator class. The fading green animation for a positive change in stock price or red for negative is achieved by animating the color of that Brush using a ColorAnimationUsingKeyFrames object added to a Storyboard. A simple Notes field for each Stock has also been added to demonstrate how a user could modify the currently selected Stock in the DataGrid using UI controls.

A screenshot of the sample application running with multiple clients connected using Firefox or Internet Explorer is shown below :

Stock Delta Server and Connected Stock Clients

Until now this type of application has typically been implemented using Ajax/Flex and the Comet Messaging Protocol with offerings in this space from applications such as Lightstreamer and Liberator. This is only a sample but it’s clear to see Silverlight already provides another option for this type of application and with the updates hopefully coming in Beta 2 and beyond in this area such as DuplexReceiver<T> the best is theoretically yet to come.

At this stage we have a substantial sample application that demonstrates how to use a variety of powerful features of Silverlight 2.0 Beta 1 for creating Rich Internet Applications (RIA) applications. This post has provided an overview of how the solution fits together but to get the most out of this sample download the full source code and explore.

Posted in Silverlight, Sockets, WCF | Tagged: , , , , , , , | 2 Comments »