When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, November 25, 2009

An Extensive Examination of LINQ: Introducing LINQ to XML

By Scott Mitchell


A Multipart Series on LINQ
This article is one in a series of articles on LINQ, which was introduced with .NET version 3.5.

  • An Introduction to LINQ - provides an overview of the purpose of LINQ, its design goals, and core components.
  • Extension Methods, Implicitly Typed Variables, and Object Initializers - looks at three language enhancements to VB and C# that, in part, allow for LINQ's unique syntax and functionality.
  • Lambda Expressions and Anonymous Types - explores two more language enhancements to VB and C# that permit LINQ's unique syntax and functionality.
  • The Ins and Outs of Query Operators - learn how query operators provide a universal approach to querying and modifying enumerable collections of data.
  • The Standard Query Operators - explore LINQ's standard query operators, a suite of built-in query operators for working with enumerable data.
  • Using the Query Syntax - learn how to write and use C# and Visual Basic's new query syntax, which lets you write LINQ queries using SQL-like syntax.
  • Grouping and Joining Data - examines the standard query operators and query syntax used to group and join data.
  • Introducing LINQ to XML - provides an overview of working with XML data using the LINQ to XML API.
  • Querying and Searching XML Documents Using LINQ to XML - examines querying and filtering XML documents using the LINQ to XML API.
  • Extending LINQ - Adding Query Operators - shows how to extend the functionality of LINQ by adding your own query operators.
  • (Subscribe to this Article Series! )

    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!

    - continued -

    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
  • A Multipart Series on LINQ
    This article is one in a series of articles on LINQ, which was introduced with .NET version 3.5.

  • An Introduction to LINQ - provides an overview of the purpose of LINQ, its design goals, and core components.
  • Extension Methods, Implicitly Typed Variables, and Object Initializers - looks at three language enhancements to VB and C# that, in part, allow for LINQ's unique syntax and functionality.
  • Lambda Expressions and Anonymous Types - explores two more language enhancements to VB and C# that permit LINQ's unique syntax and functionality.
  • The Ins and Outs of Query Operators - learn how query operators provide a universal approach to querying and modifying enumerable collections of data.
  • The Standard Query Operators - explore LINQ's standard query operators, a suite of built-in query operators for working with enumerable data.
  • Using the Query Syntax - learn how to write and use C# and Visual Basic's new query syntax, which lets you write LINQ queries using SQL-like syntax.
  • Grouping and Joining Data - examines the standard query operators and query syntax used to group and join data.
  • Introducing LINQ to XML - provides an overview of working with XML data using the LINQ to XML API.
  • Querying and Searching XML Documents Using LINQ to XML - examines querying and filtering XML documents using the LINQ to XML API.
  • Extending LINQ - Adding Query Operators - shows how to extend the functionality of LINQ by adding your own query operators.
  • (Subscribe to this Article Series! )



    ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article