Peter McGrattan’s Weblog

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

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.

About these ads

2 Responses to “Silverlight 2.0 Stock List Demo – Part 3”

  1. […] Silverlight 2.0 Stock List Demo […]

  2. ahmed said

    hello
    How can I host server(wcf) on iis b/c I am not getting any message (on other then my system )when i view in browser with my ip address

Sorry, the comment form is closed at this time.

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: