To read the article online, visit http://www.4GuysFromRolla.com/articles/112509-1.aspx

An Extensive Examination of LINQ: Introducing LINQ to XML

By Scott Mitchell


Introduction


XML is an increasingly popular way to encode documents, data, and electronic messages. There are a number of ways to programmatically create, modify, and search XML files. Since its inception, the .NET Framework's System.Xml namespace has included classes for programmatically working with XML documents. For instance, the XmlReader and XmlWriter classes offer developers a means to read from or write to XML files in a fast, forward-only manner, while the XmlDocument class allows developers to work with an XML document as an in-memory tree representation.

LINQ to XML is a new set of XML-related classes in the .NET Framework (found in the System.Xml.Linq namespace), which enable developers to work with XML documents using LINQ's features, syntax, and semantics. Compared to .NET's existing XML APIs, LINQ to XML is a simpler, easier to use API. For a given task, LINQ to XML code is typically shorter and more readable than code that uses the XmlDocument or XmlReader/XmlWriter classes. And perhaps most importantly, LINQ to XML allows you to leverage your existing knowledge and familiarity with LINQ's standard query operators and query syntax.

This article is the first in a series of articles that examines LINQ to XML. This installment introduces the LINQ to XML API, examines some of the more pertinent classes in the System.Xml.Linq namespace, and shows how to perform a number of common XML tasks using the API. Read on to learn more!

XElement - The LINQ to XML Workhorse


The most frequently used class in the LINQ to XML API is the XElement class, which represents an XML element. This class is used when programmatically constructing an XML document, when loading an XML document, and when searching, filtering, or otherwise enumerating the elements within an XML document.

For example, the XElement class has a static Load method, which loads an XML document and returns the root of the just-loaded document as an XElement object. The Load method can be passed in the path to an XML file on disk or the URL of an XML file online. You can also pass in a TextReader or XmlReader object.

The demo available for download at the end of this article includes an XML file in the App_Data folder named Inventory.xml. This XML file has a root element named <inventory> and then a variable number of <item> elements. Each <item> element represents an item in inventory and contains children elements like <name>, <unitPrice>, and <quantity>, among others. A snippet of this XML file is shown below.

<?xml version="1.0" encoding="utf-8" ?>
<inventory>
   <item>
      <name>Blue Ball</name>
      <itemNumber>7111</itemNumber>
      <unitPrice>4.95</unitPrice>
      <quantity>450</quantity>
   </item>

   <item>
      <name>Orange Octagon</name>
      <itemNumber>8888</itemNumber>
      <unitPrice>14.95</unitPrice>
      <quantity>332</quantity>
   </item>
   
   ...
</inventory>

The following code loads this XML file into an XElement object. Keep in mind that we need to pass in the full path to the XML file to load (for example, C:MySitesLINQDemosApp_DataInventory.xml). To compute this path I use the Server.MapPath method, which converts a virtual path (~/App_Data/Inventory.xml) into a physical path. For more information, see Using Server.MapPath.)

// C# - Load the XML file Inventory.xml into an XElement object
string pathToXml = Server.MapPath("~/App_Data/Inventory.xml");
XElement xml = XElement.Load(pathToXml);


' VB - Load the XML file Inventory.xml into an XElement object
Dim pathToXml As String = Server.MapPath("~/App_Data/Inventory.xml")
Dim xml As XElement = XElement.Load(pathToXml)

The XElement class includes a variety of properties and methods for working with the element's attributes and for working with the element's text value. There are also methods for getting ancestor and descendant elements. The Value property returns the concatenated text contents of the element and the text content of its descendants. For example, the Value property of the xml variable created in the above code snippet returns a string of all of the text content in the Inventory.xml file (since xml represents the root of the XML file):

Blue Ball71114.95450Orange Octagon888814.95332Red Rhombus7001-RH5.5043...

Note that all of the text is concatenated. I've used varying colors to help disambiguate where one text element ends and the next begins.

Two other helpful members of the XElement class are Elements and Element. The Elements method returns all of the child elements of the current element. You can optionally pass in an element name and then only those children element with a matching name are returned. The Element method requires a name as an input parameter and then returns the first child element with that name.

The following snippet shows these two methods in action. The example starts by enumerating through the children elements of the root element (namely, visiting each <item> element. For each <item> element the <name> and <unitPrice> elements are grabbed via the Element method and their text values are read into a variable via their Value properties:

// C# - enumerate through the root element's children elements
foreach (XElement itemElement in xml.Elements())
{
   // Read the values from the <name> and <unitPrice> elements
   string name = itemElement.Element("name").Value;
   decimal unitPrice = Convert.ToDecimal(itemElement.Element("unitPrice").Value);
   
   ...
}


' VB - enumerate through the root element's children elements
For Each itemElement As XElement In xml.Elements()
   ' Read the values from the <name> and <unitPrice> elements
   Dim name As String = itemElement.Element("name").Value
   Dim unitPrice As Decimal = Convert.ToDecimal(itemElement.Element("unitPrice").Value)
   
   ...
Next

Understand that the Elements method returns a collection of XElement objects as type IEnumerable<XElement>. As we've discussed throughout this article series, LINQ is designed to work with collections of data of type IEnumerable<T>. Consequently, we can use LINQ's standard query operators and C# and Visual Basic's query syntax when working with LINQ to XML. The above loop can be rewritten using the query syntax. The following demo shows using the query syntax to bind the name and price of each inventory item to a BulletedList control. The ASP.NET page contains a BulletedList control named blInventoryItems whose DataTextValue property is set to "NameAndPrice". The query syntax code enumerates through each <item> element (via xml.Elements()) and for each <item> element it returns an object with a single string property named NameAndPrice that is assigned the value "Name (Price)".

// C#
blInventoryItems.DataSource = from elem in xml.Elements()
                      select new { NameAndPrice = string.Format("{0} ({1:c})",
                                       elem.Element("name").Value,
                                       Convert.ToDecimal(elem.Element("unitPrice").Value)
                                    ) };

blInventoryItems.DataBind();

' VB
blInventoryItems.DataSource = From elem In xml.Elements() _
                      Select New With {.NameAndPrice = String.Format("{0} ({1:c})", _
                                       elem.Element("name").Value, _
                                       Convert.ToDecimal(elem.Element("unitPrice").Value) _
                                    )}

blInventoryItems.DataBind()

The screen shot below shows the BulletedList control when viewed through a browser.

The inventory items' names and prices are displayed in a bulleted list.

Creating XML


The XElement class can also be used to create XML content. One of the XElement constructor overloads accepts as input a name for the element and an array of objects that defines the element's content. This constructor can be used in a terse and human-readable format to programmatically generate XML content. For instance, the following snippet of C# code programmatically creates XML content whose structure mirrors that of the Inventory.xml file:

// C# - Create XML content
XElement inventoryXML =
   new XElement("inventory",
      new XElement("item",
         new XElement("name", "Teal Trapezoid"),
         new XElement("itemNumber", "TTTT-T"),
         new XElement("unitPrice", "8.25"),
         new XElement("quantity", "414")
      ),
      new XElement("item",
         new XElement("name", "Puce Parallelogram"),
         new XElement("itemNumber", "4400-P"),
         new XElement("unitPrice", "9.99"),
         new XElement("quantity", "97")
      ),
      new XElement("item",
         new XElement("name", "Powder Blue Prism"),
         new XElement("itemNumber", "3110-PBP"),
         new XElement("unitPrice", "9.50"),
         new XElement("quantity", "12")
      )
   );

The above code is both terse and readable. You can determine the structure and contents of the XML being created by browsing the code. And things get even cooler if you are using Visual Basic, which supports XML literals. While you can certainly create XML content in VB using virtually identical syntax to that shown above, VB's XML literals allow for even more readable code. The following snippet creates the same XML content, but uses XML literals:

Dim inventoryXML As XElement = _
   <inventory>
      <item>
         <name>Teal Trapezoid</name>
         <itemNumber>TTTT-T</itemNumber>
         <unitPrice>8.25</unitPrice>
         <quantity>414</quantity>
      </item>
      <item>
         <name>Puce Parallelogram</name>
         <itemNumber>4400-P</itemNumber>
         <unitPrice>9.99</unitPrice>
         <quantity>97</quantity>
      </item>
      <item>
         <name>Powder Blue Prism</name>
         <itemNumber>3110-PBP</itemNumber>
         <unitPrice>9.50</unitPrice>
         <quantity>12</quantity>
      </item>
   </inventory>

Modifying XML Content


The XElement class's SetAttributeValue and SetElementValue methods are used to modifying attribute or text values for an element. For example, the following code snippet doubles the price of every item in the Inventory.xml file:

// C# - Double the price for each item in the inventory...
foreach (XElement item in xml.Elements())
{
   // First, get the current price
   decimal currentPrice = Convert.ToDecimal(item.Element("unitPrice").Value);

   // Now double it!
   item.SetElementValue("unitPrice", currentPrice * 2);
}


' VB - Double the price for each item in the inventory...
For Each item As XElement In xml.Elements()
   ' First, get the current price
   Dim currentPrice As Decimal = Convert.ToDecimal(item.Element("unitPrice").Value)

   ' Now double it!
   item.SetElementValue("unitPrice", currentPrice * 2)
Next

Here, xml is the root element of the Inventory.xml file. The above code starts by enumerating all of the root element's children via a foreach loop. For each child, we get the <unitPrice> value and then double it via a call to SetElementValue. When this loop completes, each item in the inventory will have had its <unitPrice> value doubled.

Saving XML


Keep in mind that in the code snippets above any XML content created or modified via the XElement class's methods are in-memory changes. In other words, in the preceding example where we doubled each price, that code does not actually modify the contents of the Inventory.xml file. In other words, when we load XML content via a call to XElement.Load(pathToXml), what's happening is that the XML content is being read from disk into an XElement object in memory. Any changes to that XElement object - such as doubling the price - is a change made to that XElement object in memory. The change is not persisted back to the Inventory.xml file unless we explicitly save the XML content back to disk.

To save XML content to disk use the XElement class's Save method. You can pass this method a TextWriter object, an XmlWriter object, or a string that specifies the file path to where the content should be written. The following snippet saves the contents of an XElement to a file named XMLOutput.xml in the same folder as the ASP.NET page that made this call.

XElementObject.Save(Server.MapPath("XMLOutput.xml"));

Returning to the double prices example, to save the doubled prices back to the Inventory.xml file you could use C# code similar to the following. (Download the demo available at the end of this article to see this code snippet in Visual Basic.)

// C# - Start by reading in the Inventory.xml contents
string pathToXml = Server.MapPath("~/App_Data/Inventory.xml");
XElement xml = XElement.Load(pathToXml);


// Now loop through each element and double the price
foreach (XElement item in xml.Elements())
{
   // First, get the current price
   decimal currentPrice = Convert.ToDecimal(item.Element("unitPrice").Value);

   // Now double it!
   item.SetElementValue("unitPrice", currentPrice * 2);         
}

// Finally, save the XML back to the Inventory.xml file
xml.Save(pathToXml);

The demo available for download includes a web page named ModifyXML.aspx that allows users to double or halve prices with the click of a button. The screen show below shows the page's output immediately after the user has clicked the "Double Prices" button.

The inventory items' prices have been doubled!

Looking Forward...


This article focused on the XElement object and techniques for creating, loading, and modifying XML content. We've only scratched the surface of the LINQ to XML API, as we've yet to look at how to add and remove XML elements, how to deal with namespaces, how to do XML transforms, and how to perform standard LINQ operations against XML, such as projections, filtering, aggregating, and so forth. These topics and more will be covered in future articles in this series.

Until then... Happy Programming!

  • By Scott Mitchell


    Attachments:


  • Download the code associated with this article series
  • Further Reading


  • LINQ to XML (technical documentation)
  • Working with XML Data Using LINQ, a TreeView, and a ListView
  • LINQ to XML - 5 Minute Overview
  • Article Information
    Article Title: ASP.NET.An Extensive Examination of LINQ: Introducing LINQ to XML
    Article Author: Scott Mitchell
    Published Date: November 25, 2009
    Article URL: http://www.4GuysFromRolla.com/articles/112509-1.aspx


    Copyright 2017 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers