Peter McGrattan’s Weblog

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

Posts Tagged ‘LINQ to XML’

Silverlight 2.0 Stock List Demo – Part 2

Posted by petermcg on April 24, 2008

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

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

Part Two : From XML file to DataGrid via WCF – (Code download)

Part 1 of this post showed a simple Silverlight application that populates a DataGrid with sample financial stock information read from an XML file extracted from a .xap package using LINQ to XML.

This post shows how to get the same stock information from an XML file and bind it to a DataGrid, but this time the data does not come from the client deployment package but instead from a WCF web service.

Here’s a look at the Visual Studio 2008 solution for Part 2 :

Solution Structure

Now that the data is coming from the server, the Stocks.xml file containing 1000 sample stocks is located under the App_Data folder as part of the web site.

Also added to the web site is the WCF web service ‘StockService’. Support for WCF web services is provided in the System.ServiceModel namespace new to Silverlight 2, for a good introduction to WCF in Silverlight 2 have a look here, be sure to note the required change from wsHttpBinding to basicHttpBinding for Beta 1.

StockService implements the GetStocks() method and returns a generic list of Stock objects using the XmlFileStockService class that remains unchanged. In order to get the path to open the XML file from within the WCF service the HostingEnvironment.ApplicationPhysicalPath property is used. In ASMX services the same goal could be achieved using HttpContext.Current.Server.MapPath, however WCF services are host agnostic so unless your web site is running in aspnetCompatibilityMode the HttpContext will be null.

The Stock type is able to be returned and consumed from GetStocks() because it has been marked with the [DataContract] attribute, making it serializable by a serializer such as the DataContractSerializer :

[DataContract]
public class Stock
{
    [DataMember]
    public string Symbol { get; set; }
    [DataMember]
    public double Bid { get; set; }
    [DataMember]
    public double Ask { get; set; }
}

If these attributes are removed and you try to ‘Add a Service Reference’ to the StockService web service from the Silverlight client application an exception is thrown with the message : ‘Metadata contains a reference that cannot be resolved’.

Our Silverlight client application is manifest by the StockListDemo.App project. ‘Add Service Reference Support’ and Web Services Client functionality is again new to Silverlight 2 and this project takes advantage of that by adding a Service Reference to our WCF StockService called WcfStockService, this is also the final part of a new namespace for the generated proxy in Reference.cs. You can view Reference.cs by clicking on ‘Show All Files’ when the WcfWebService service reference is selected, some of the generated code is worth a look, notice for example that the Stock class implements the INotifyPropertyChanged interface.

The UI for the Silverlight client is still defined in Page.xaml and still consists mainly of a DataGrid control. We are no longer relying on setting AutoGenerateColumns="True" due to a bug in Beta 1. If you specify AutoGenerateColumns="true" at present and then bind to a collection with a Count of 0 and exception is raised with the message "Enumeration has either not started or has already finished", to avoid this exception we define our own columns of type DataGridTemplateColumn as follows :

<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>
                    <TextBlock Text="{Binding Bid}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn Header="Ask">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Ask}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
    </data:DataGrid.Columns>
</data:DataGrid>


With the web service in place and the UI designed, all that remains is the code in Page.xaml.cs and StockTicker.cs that calls the web service and populates the UI with the results, firstly Page.xaml.cs :

public partial class Page : UserControl
{
    private readonly StockTicker stockTicker = new StockTicker();

    public Page()
    {
        InitializeComponent();

        StocksGrid.ItemsSource = stockTicker.StockList;

        stockTicker.GetStocksAsync();
    }
}

Notice the order in which things are happening here : we are binding the DataGrid to the empty StockList collection property first, then forwarding an asynchronous call to the webservice using the GetStocksAsync() method of the stockTicker field. Because the collection the DataGrid is binding to is an ObservableCollection<WcfStockService.Stock>, the DataGrid will populate itself when this collection notifies that it has changed. Download the entire project using the link at the start to see this in action. The code for the StockTicker class is below:

public class StockTicker
{
    private ObservableCollection<WcfStockService.Stock> stockList = new ObservableCollection<WcfStockService.Stock>();
    public ObservableCollection<WcfStockService.Stock> StockList
    {
        get { return stockList; }
    }

    public void GetStocksAsync()
    {
        BasicHttpBinding binding = new System.ServiceModel.BasicHttpBinding();
        binding.MaxReceivedMessageSize = int.MaxValue;

        EndpointAddress endpoint = Utils.GetHostEndpointAddress("StockService.svc");

        WcfStockService.StockServiceClient proxy = new WcfStockService.StockServiceClient(binding, endpoint);
        proxy.GetStocksCompleted += new EventHandler<WcfStockService.GetStocksCompletedEventArgs>(OnGetStocksCompleted);
        proxy.GetStocksAsync();
    }

    private void OnGetStocksCompleted(object sender, WcfStockService.GetStocksCompletedEventArgs e)
    {
        e.Result.ForEach(stock => StockList.Add(stock));
    }
}

The StockList collection is initialised when the class is instantiated and the property is populated in the handler for the web service’s GetStocksCompleted event. In order to use the ForEach(Action<T> action) generic method to populate the collection, the Collection Type for the proxy generation code has been changed from the default of Array to System.Collections.Generic.List:

Proxy Collection Type

The handler for the GetStocksCompleted event is wired up in the GetStocksAsync() method along with the creation of objects for the webservice binding and endpoint. The endpoint address is not hardcoded but instead retrieved based on the Uri of the current HtmlPage in the custom Utils.GetHostEndpointAddress method. Also notice that we are setting the MaxReceivedMessageSize property of our binding object, in fact this is the whole reason we are creating a Binding object and an EndpointAdress object and using them in the creation of the StockServiceClient instead of just calling the service’s default parameter-less constructor. The MaxReceivedMessageSize property is initially set to 65536 (64 kilobytes), if you try to return more data than this from a WCF web service without increasing this limit at present, a QuotaExceededException is thrown with the message MaxReceivedMessageSizeExceeded :

Quota Exceeded Exception

For more on this have a look here, especially the second post. The Stocks.xml file contains 1000 stock objects after all so to enable all this data to be returned from the service the MaxReceivedMessageSize property is set to int.MaxValue although it doesn’t need to be set that high for production where DOS issues could be a concern. Interestingly this property is actually of type long or Int64 but if you try to set it to long.MaxValue for example, an ArgumentException is thrown with the message MaxReceivedMessageSizeMustBeInIntegerRange. As a last word on the MaxReceivedMessageSize property, notice that there is a setting in the ServiceReferences.ClientConfig file generated by adding a Service Reference :

<binding name="BasicHttpBinding_IStockService" maxBufferSize="65536"
    maxReceivedMessageSize="65536">
    <security mode="None" />
</binding>

There is a known bug that changing these values has no effect in Beta 1, you have to change the property in code as shown above, this will obviously be resolved at some point in a future release.

Now the Silverlight UI is wired up to the WCF service via the StockTicker and the results appear in the DataGrid things are looking good. To prove that the INotifyPropertyChanged interface on the generated Stock class and the ObservableCollection of those Stocks is working as expected, a textbox and button have been added to allow modification of the DataGrid SelectedItem’s Symbol name. Notice that the code in the handler for the button’s Click event merely changes the value of the selected Stock’s Symbol property and the DataGrid updates automatically. This is purely for educational value of course, normally a symbol name would not change :

Stock List DataGrid

To progress with our sample financial RIA stock ticker application the next area to look at is updating the Stock 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 the listening client. There is support for this type of duplex communication with the application’s host of origin in Silverlight 2 Beta 1 provided by the Sockets class in the System.Net networking namespace. Depending on feedback this will be the subject of a future post.

Posted in LINQ to XML, Silverlight, WCF | Tagged: , , , , , | 2 Comments »

Silverlight 2 Stock List Demo – Part 1

Posted by petermcg on April 17, 2008

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

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

Part One : From XML file to DataGrid via LINQ to XML – (Code download)

One of the great features new to Silverlight 2.0 Beta 1 is the support for LINQ to XML available in the System.Core and System.Xml.Linq assemblies. Part 1 of this post shows a simple Silverlight application that populates a DataGrid with sample financial stock information read from an XML file using LINQ to XML.

This is the layout of our initial Visual Studio 2008 solution structure :

Solution Structure

Our solution contains three projects, the Silverlight Application project StockListDemo.App and it’s hosting Web Site, and a Silverlight Class Library (StockLibrary). Notice in our Silverlight Application we have a folder called Data containing an XML file pre-populated as follows :

<?xml version="1.0" encoding="utf-8" ?>
<stocks>
  <stock symbol="AAHH" bid="256.02" ask="256.70" />
  <stock symbol="AAPM" bid="181.31" ask="182.03" />
  <stock symbol="AAZG" bid="286.51" ask="286.93" />
  <stock symbol="ABOO" bid="226.74" ask="227.69" />
  <stock symbol="ADJS" bid="109.35" ask="109.92" />
  <stock symbol="AELX" bid="91.52" ask="92.43" />
  ...
</stocks>

Each <stock /> element in the Stocks.xml file consists of a symbol name for the stock, a bid price and an ask price. Although it’s easy to see where the file resides at design time, it’s not immediately obvious how to access it at runtime. When in this situation it’s a good idea to check the ‘Build Action’, you can do this by clicking on the file and pressing F4 :

Stocks.xml Build Action

When you add a new XML file to a Silverlight Application in Visual Studio 2008 the Build Action is set to ‘Content’ by default which means the file and it’s folder structure will be packaged at the root of the Silverlight .xap deployment package. A .xap file is packaged when you build a Silverlight Application by the Chiron.exe SDK tool and is really just a .zip file. If we open it in a .zip file application after a build you can see the Data folder containing the .xml file :

Stocks.xml in Xap Extract

Now we know where the file is at runtime, the question is how do we get at it and read it. Here’s one solution, but the title of this post says LINQ to XML so here’s the code to read the 1000 stock elements from the XML file in the .xap and assign the results to a collection :

public List<Stock> GetStocks()
{
    List<Stock> stocks = new List<Stock>();

    XElement doc = XElement.Load("Data/Stocks.xml");

    stocks = (from el in doc.Elements()
              select GetStock(el)).ToList();

    return stocks;
}

private Stock GetStock(XElement el)
{
    Stock s = new Stock();
    s.Symbol = el.Attribute("symbol").Value;
    s.Bid = Convert.ToDouble(el.Attribute("bid").Value);
    s.Ask = Convert.ToDouble(el.Attribute("ask").Value);
    return s;
}

The two methods above are part of the XmlFileStockService class in the StockLibrary project, you can download the whole small example solution below. The GetStocks method is required by the IStockService interface which the XmlFileStockService class implements. Another class in the same assembly is the StockTicker class. StockTicker has a private field of type IStockService and calls GetStocks on this field to expose the collection in the StockList property. Page.xaml has an instance of StockTicker and assigns it’s StockList property to the ItemsSource property of a DataGrid called StocksGrid with AutoGenerateColumns="True" :

Stock List DataGrid

In a real world situation this data would come from a source such as a web service, now that Silverlight supports cross-domain calls, the data for this type of RIA application can really come from a wide variety of sources. This is a sample application but with the IStockService interface has still been designed to require a minimum of changes for swapping out the datasource from an XML file to a WCF service for example. Some of the areas I intend to take a look at in future posts in this series are : incorporating a web service as a datasource, getting the stock list to tick with modifications and additions and more.

Posted in LINQ to XML, Silverlight | Tagged: , , , , | 2 Comments »

 
Follow

Get every new post delivered to your Inbox.