November, 2009

Linq2Xml Example

This article is a short primer on the proper usage and the power of Linq2Xml. To demonstrate Linq2Xml usage, I'm going to build a Console application that captures and then queries against some public information about a few U.S. stocks. This article is a good xml example and a c# example for Linq to Xml.

Visual Studio 2008 New Project Window

I'll start by creating a new Console application (image shows Visual Studio 2008). I called mine "Linq2Xml_Example," but you can call yours whatever you choose, of course.

The first thing I want to do is find a data source for our stock data. Create a new "Service Reference" and point it to http://www.webservicex.net/stockquote.asmx. I called mine "wsQuote," but you do whatever feels right. This is an Xml Web Service that I found by Googling. It's not always up and running, but it's been mostly reliable, and the price is right.

Visual Studio 2008 Add Service Reference Window

Next, let's test our connection by checking the current price of Apple. It took a little experimentation to find the right function within the web service to call, but ultimately, it was pretty straight-forward. Here's a picture what my console application looks like now:

namespace Linq2Xml_Example
{
    class Program
    {
        static void Main(string[] args)
        {
            wsQuote.StockQuoteSoapClient quote =
                new wsQuote.StockQuoteSoapClient("StockQuoteSoap");
            string result = quote.GetQuote("AAPL");
            Console.WriteLine(result);
            Console.Read();
        }
    }
}

If all is going according to plan, you will see an xml string. The Console.Read() function allows us to test using F5 and see the results instead of watching them flicker away in the background. The Xml code that I get back from the service, after it's been formatted, looks like this:

<StockQuotes>
  <Stock>
    <Symbol>AAPL</Symbol>
    <Last>201.46</Last>
    <Date>11/9/2009</Date>
    <Time>4:00pm</Time>
    <Change>+7.12</Change>
    <Open>196.95</Open>
    <High>201.90</High>
    <Low>196.26</Low>
    <Volume>18887668</Volume>
    <MktCap>181.5B</MktCap>
    <PreviousClose>194.34</PreviousClose>
    <PercentageChange>+3.66%</PercentageChange>
    <AnnRange>78.20 - 208.71</AnnRange>
    <Earns>6.289</Earns>
    <P-E>30.90</P-E>
    <Name>Apple Inc.</Name>
  </Stock>
</StockQuotes>

While interesting, it's not all that useful by itself. So, let's take a step back, encapsulate this logic into a class, and expand it to capture a wee bit more data.

internal class StockData
{
    private static StockData _stockData = null;
    public static StockData GetData()
    {
        if (_stockData == null) _stockData = new StockData();
        return _stockData;
    }
    private StringBuilder _xml = new StringBuilder();

    public string ToXml()
    {
        return String.Format("<Stocks>{0}</Stocks>", _xml.ToString());
    }
    private StockData()
    {
        wsQuote.StockQuoteSoapClient quote =
	    new wsQuote.StockQuoteSoapClient("StockQuoteSoap");

        string[] stocks = new string[] { "AAPL", "MSFT", "X", "C", "FUQI" };
        foreach (string stock in stocks)
        {
	        string results = quote.GetQuote(stock);
	        if (!string.IsNullOrEmpty(results))
	        {
	            XElement xSQ = XElement.Parse(results);
	            if (xSQ != null)
	            {
		            var xStockNodes = xSQ.Descendants("Stock");
		            if (xStockNodes != null) 
		                _xml.Append(xStockNodes.First());
	            }
	        }
        }
    }
}

Okay, so now I'm capturing some data for a handful of arbitrary stocks, but before I move on, let's take a look at how easily this data was handled. First, there's the XElement.Parse() function, which is a big improvement in the .Net world, allowing us to instantly load our Xml string into the XElement hierarchy. Second, I have the var keyword, which I would not have used other than to write about it here. var allows for run-time type casting. The XElement.Descendants() function returns an IEnumerable<XElement> object of all of the <Stock> elements. Hopefully, there is one and only one such element, but I do a little checking to make sure that I don't get the dreaded Object not set to an instance of an object error. Here is our new xml:

<Stocks>
  <Stock>
    <Symbol>AAPL</Symbol>
    <Last>201.46</Last>
    <Date>11/9/2009</Date>
    <Time>4:00pm</Time>
    <Change>+7.12</Change>
    <Open>196.95</Open>
    <High>201.90</High>
    <Low>196.26</Low>
    <Volume>18887668</Volume>
    <MktCap>181.5B</MktCap>
    <PreviousClose>194.34</PreviousClose>
    <PercentageChange>+3.66%</PercentageChange>
    <AnnRange>78.20 - 208.71</AnnRange>
    <Earns>6.289</Earns>
    <P-E>30.90</P-E>
    <Name>Apple Inc.</Name>
  </Stock>
  <Stock>
    <Symbol>MSFT</Symbol>
    <Last>28.99</Last>
    <Date>11/9/2009</Date>
    <Time>4:00pm</Time>
    <Change>+0.47</Change>
    <Open>28.60</Open>
    <High>29.00</High>
    <Low>28.53</Low>
    <Volume>57522020</Volume>
    <MktCap>257.4B</MktCap>
    <PreviousClose>28.52</PreviousClose>
    <PercentageChange>+1.65%</PercentageChange>
    <AnnRange>14.87 - 29.35</AnnRange>
    <Earns>1.539</Earns>
    <P-E>18.53</P-E>
    <Name>Microsoft Corpora</Name>
  </Stock>
  <Stock>
    <Symbol>X</Symbol>
    <Last>38.71</Last>
    <Date>11/9/2009</Date>
    <Time>4:00pm</Time>
    <Change>+1.33</Change>
    <Open>38.03</Open>
    <High>38.97</High>
    <Low>38.03</Low>
    <Volume>8983488</Volume>
    <MktCap>5.549B</MktCap>
    <PreviousClose>37.38</PreviousClose>
    <PercentageChange>+3.56%</PercentageChange>
    <AnnRange>16.66 - 51.65</AnnRange>
    <Earns>-6.613</Earns>
    <P-E>N/A</P-E>
    <Name>UNITED STATES STE</Name>
  </Stock>
  <Stock>
    <Symbol>C</Symbol>
    <Last>4.19</Last>
    <Date>11/9/2009</Date>
    <Time>4:00pm</Time>
    <Change>+0.13</Change>
    <Open>4.14</Open>
    <High>4.19</High>
    <Low>4.10</Low>
    <Volume>234101664</Volume>
    <MktCap>95.800B</MktCap>
    <PreviousClose>4.06</PreviousClose>
    <PercentageChange>+3.20%</PercentageChange>
    <AnnRange>0.97 - 12.28</AnnRange>
    <Earns>-2.782</Earns>
    <P-E>N/A</P-E>
    <Name>CITIGROUP INC</Name>
  </Stock>
  <Stock>
    <Symbol>FUQI</Symbol>
    <Last>19.18</Last>
    <Date>11/9/2009</Date>
    <Time>4:00pm</Time>
    <Change>-4.15</Change>
    <Open>24.78</Open>
    <High>25.74</High>
    <Low>18.83</Low>
    <Volume>10613366</Volume>
    <MktCap>529.9M</MktCap>
    <PreviousClose>23.33</PreviousClose>
    <PercentageChange>-17.79%</PercentageChange>
    <AnnRange>3.31 - 32.68</AnnRange>
    <Earns>1.668</Earns>
    <P-E>13.99</P-E>
    <Name>Fuqi Internationa</Name>
  </Stock>
</Stocks>

Okay, so let's do something useful. Let's say that I want to sort the list by price. I might try:

class Program
{
    static void Main(string[] args)
    {
        XElement xStocks = XElement.Parse(StockData.GetData().ToXml());

        IEnumerable<XElement> stocks = (from x in xStocks.Descendants("Stock")
            orderby
            Convert.ToDecimal(((XElement)x).Descendants("Last").First().Value)
            select x);

        foreach (XElement stock in stocks)
        {
            Console.WriteLine(stock.Descendants("Symbol").First().Value.PadLeft(10)
                + ": " + stock.Descendants("Last").First().Value.PadLeft(10));
        }

        Console.Read();
    }
}

Note the orderby keyword and the fact that I can convert the content of the Last element under the x variable being selected. I have to use the First() method because Descendants returns a collection. Even though there is only one Last element, First() is required to fetch it. If there was no Last element, this code would throw an Exception. It's probably a better practice to make sure the Count() is greater than zero, but I'm trusting that the Xml comes back from the server consistently. The output looks like this:

         C:       4.19
      FUQI:      19.18
      MSFT:      28.99
         X:      38.71
      AAPL:     201.46

Let's say that I just want to identify the stocks in the list that are down for the day. I can use the where keyword, convert the Change element to a decimal and compare it to zero, like so:

class Program
{
    static void Main(string[] args)
    {
        XElement xStocks = XElement.Parse(StockData.GetData().ToXml());

        IEnumerable<XElement> stocks = (from x in xStocks.Descendants("Stock")
            where 
            Convert.ToDecimal(x.Descendants("Change").First().Value) < 0
            select x);

        foreach (XElement stock in stocks)
        {
            Console.WriteLine(stock.Descendants("Symbol").First().Value.PadLeft(10)
                + ": " + stock.Descendants("Last").First().Value.PadLeft(10)
                + ": " + stock.Descendants("Change").First().Value.PadLeft(10));
        }

        Console.Read();
    }
}

Our results become:

      FUQI:      19.18:      -4.15

In this article, I've looked at referencing an Xml Web Service, loading Xml into the XElement object, the orderby and where keywords, and manipulating Xml elements during the execution of the Linq statement.

In summary, Linq2Xml is incredibly powerful when it comes to searching and parsing large amounts of Xml, especially when data values need to be manipulated, sorted or compared. I hope this short primer helps you get started on making practical use of Linq2Xml for yourself.

 
Your feedback helps me provide content that is meaningful. Please don't be shy about speaking your mind.
Please see other tidbits.

Please provide feedback about this article

Email: (optional)