LINQ to XML, which makes query a first class construct in C# and Visual Basic, is the new XML API in the .NET Framework 3.5.

With the introduction of Language Integrated Query (LINQ), Microsoft is introducing LINQ implementations that work over objects, data, and XML. LINQ to XML improves on System.Xml in the .NET Framework 2.0 by being both simpler to use and more efficient. Microsoft developed this new API because the W3C-based DOM API does not integrate well into the LINQ programming model.

Windows Live Expo, an online service for buying and selling merchandise, provides a set of Web services that enable programmatic access to the service’s listings, and returns results as XML. In this article, I’ll show you how to build a Web page that searches and displays items from the Windows Live Expo listings. To make the page a little more interesting, I’ll show you how to integrate the Virtual Earth map control and use it to display the locations of the items found. Figure 1 shows the sample application in final form.

Figure 1: Sample application browsing Windows Live Expo data and mapping results using Microsoft Virtual Earth. LINQ to XML ties everything together.

Because Windows Live services produce and consume XML data, they are ideal candidates to showcase the power of LINQ to XML. Let me show you how easily you can build an application using LINQ to XML.

Loading XML and from the Web

You need an application ID in order to use the Windows Live Expo API, which you can get by simply signing up on the Live Expo site. Once you have an application ID, use the GetCategories function to obtain a list of all category names and their associated IDs. Your application needs these IDs to search the online listings.

To make the Web page easier to use, I’ll DataBind the categories and IDs to an ASP.NET DropDownList control. The code snippet below shows how to obtain the categories in XML format.

C#

var d = XDocument.Load(
@"http://expo.live.com/API/" +
"Classifieds_GetCategories.ashx?appKey=" +
"-1690295249159343350");

Visual Basic

Dim d = XDocument.Load("http://expo.live.com/" _
  + "API/Classifieds_GetCategories.ashx?appKey=" _
  + "-1690295249159343350")

We load XML using the static Load method, available on both XDocument and XElement. The example above uses XDocument.Load, however, it could also have used XElement.Load because the XML is both a valid XDocument and XElement.

The XML that was returned has a root element named categories and a list of category elements with ID and name attributes. The application will use the id attribute to query Windows Live Expo for items in each category.

<e:categories
xmlns:e="http://e.live.com/ns/2006/1.0">
  <e:category e:id="2" e:name="Autos">
  <e:category e:id="4" e:name="Boats">
  <e:category e:id="3" e:name="Cars and Trucks">
  <e:category e:id="5" e:name="Motorcycles">

You can write a LINQ query using the Descendants method to get the category names and IDs. LINQ to XML supports all of the standard XML axes such as Elements, Attributes, Descendants, and Ancestors. Here I used the Descendants method because category elements can be nested within other category elements.

Below is the complete query.

C#

XNamespace expo =
   "http://expo.live.com/ns/2006/1.0";
    
var categories = from c in d.Descendants(expo +
   "category")
       select new
       {
            Name = (string)c.Attribute(expo +
                  "name"),
            Value = (string)c.Attribute(expo +
                  "id")
       };
    
 CategoryList.DataSource = categories;
 CategoryList.DataBind();

The expression:

d.Descendants(expo + "category")

returns an IEnumerable of XElements that match the name. For each element returned, the select clause creates an object with two members, Name and Value. Because the type is not named and declared, it is an anonymous type created by the compiler.

Assigning the query result to the CategoryList’s DataSource property and calling DataBind displays the results in the page’s DropDownList control.

XML namespaces have traditionally been a source of confusion. One of the goals of LINQ to XML was to simplify XML namespace handling.

XML namespaces have traditionally been a source of confusion. One of the goals of LINQ to XML was to simplify XML namespace handling. LINQ to XML solves the problem with two new classes called XNamespace and XName. You can create an XNamespace using the static method XNamespace.Get. However, for convenience the class also has an implicit conversion from string to XNamespace. You can initialize an XNamespace variable by assigning the namespace uri string to the variable.

XNamespace expo =
   "http://expo.live.com/ns/2006/1.0";

In LINQ to XML, all names are represented by the XName class, a fully qualified name. That is, it always has both a namespace and a local name. To get an XName object requires an XNamespace object because local names are always allocated with respect to some namespace. Here’s an example of getting a name from the expo namespace.

expo.GetName("category")

However, you won’t see this code in the sample application. For convenience, LINQ to XML overloads the + operator. You can get a fully qualified name using the simpler code shown.

expo + "category"

Now creating and using XML names and namespaces is really simple.

You may have noticed the cast to string when getting the attributes. Calling the Attribute method returns an XAttribute object, but I want the attribute’s value. The XAttribute class has a Value property; however, it is rarely used. Instead, the XAttribute and XElement classes define a number of explicit conversions for converting objects to any of the common CLR types. In this case I want a string but I could have also cast to double, decimal or datetime if those value types were stored in the attribute.

Visual Basic Xml Properties

As part of the .NET Framework 3.5, all of the features of the LINQ to XML API are available in both C# and Visual Basic. However, Visual Basic 9.0 includes additional language features to make XML processing even simpler. One of those features is XML properties. Now, you can directly query XML in Visual Basic without using an API.

As part of the .NET Framework 3.5, all of the features of the LINQ to XML API are available in both C# and Visual Basic. However, Visual Basic 9.0 includes additional language features to make XML processing even simpler.

An XML property lets you treat a sub element or attribute as if it were an ordinary CLR property. Syntactically, XML element properties differ from normal properties by enclosing the name in angle brackets. XML attribute properties are prefixed with an ‘@’ character. Below you can see the same LINQ query in Visual Basic using element and attribute properties to get the category names and ids.

Visual Basic

Dim categories =
      From c In d...<expo:category> _
      Select Name = c.@expo:name, _
                    Value = c.@expo:id
    
CategoryList.DataSource = categories
CategoryList.DataBind()

What happened to the calls to Descendants and Elements? In addition to special syntax for element and attribute names there is also special syntax for common XML query axes. Table 1 shows how LINQ to XML methods map to Visual Basic property syntax.

LINQ to XML has both an Element method and an Elements method. The former returns a single element while the latter returns a collection. However, the XML element property in Visual Basic maps to the Elements method not the Element method. That means XML element properties are always a collection.

In Visual Basic you do not have to use the XML property syntax but it is usually more convenient and compact than using the API directly. Sometimes you have to use the API because there are many methods in LINQ to XML that do not have a special syntax in Visual Basic such as Ancestors.

In the example, the element and attribute names are qualified. In C# you saw how to create fully qualified names using an XNamespace object along with implicit string conversions. Visual Basic goes one step further and extends the imports statement to support XML namespaces in addition to CLR namespaces. You do not have to define any XNamespace objects in the code. Just add an imports statement for each XML namespace you need in the code. After that you can use the prefix anywhere in the source file. Below you can see how to define the expo and geo prefix in Visual Basic.

Imports <xmlns:expo="http://expo.live.com...">
Imports <xmlns:geo="http://www.w3.org/2003...">

Querying for the Items

Now that you have the categories, I’ll show you how to write the code to search for the items on Windows Live Expo. I’ll use the ListingsByCategoryKeywordLocation method to do that. This method takes a category, a set of keywords, a location, either latitude and longitude or a postal code, a maximum distance to include in the search, and a maximum number of results. You’ll get this information from the user from the controls on the page.

With the parameterized URL constructed you can load the XML data from Live Expo. The data comes back as RSS with some Windows Live Expo extension elements. In the code I’ll DataBind these items to an ASP.NET ListView control. To get the items, the query uses the Elements method and explicitly drills into the RSS data from the root element. For each item, the query creates an anonymous type with the title, published date, URL to the item’s Web page, location, and price. Again, each element’s value is retrieved by explicitly casting the element to the desired CLR type. The result is then bound to an ASP.NET ListView to display on the Web page.

C#

var d = XDocument.Load(BuildListingUrl());
    
var items = from item in
d.Elements("rss").Elements("channel").
                                 Elements("item")
    select new
    {
    Title = (string)item.Element("title"),
    Published = (DateTime)item.Element("pubDate"),
    Url = (string)item.Element("link"),
    Location = (string)item.Element(expo +
   "location").Element(expo + "city"),
     Price = (decimal)item.Element(expo + "price")
    };
    
 ListView1.DataSource = items;
 ListView1.DataBind();

The Visual Basic code is nearly the same. Just as in C#, the element values are cast to the desired type. However, in Visual Basic there is an explicit call to the Value method because the XML element property is mapped to the Elements method. This method returns a collection of XElements. While XElement defines explicit conversions to CLR types, the collection does not. To solve this problem, Visual Basic defined a Value extension method on the collection. This method gets the first item from the collection and returns its value. If the collection is empty, it returns Nothing.

Visual Basic

Dim d = XDocument.Load(BuildListingUrl())
    
Dim items =
  From item In d.<rss>.<channel>.<item> _
  Select _
    Title = item.<title>.Value, _
    Published = CDate(item.<pubDate>.Value), _
    Url = item.<link>.Value, _
    Location = _
      item.<expo:location>.<expo:city>.Value, _
    Price = CDec(item.<expo:price>.Value)
    
 ListView1.DataSource = items
 ListView1.DataBind()

XML IntelliSense

Visual Basic makes it even easier to use XML properties because it supports XSD-driven IntelliSense. If you add an XSD file to the project, IntelliSense will pop up after typing a ‘<’ or ‘@’ in an expression of type XElement or XDocument as shown in Figure 3. If you don’t have a schema for your XML data, that’s not a problem. Just load some sample XML into the XML Editor in Visual Studio and infer the schema. Save the inferred schema to your project directory and then add the file to your project.

Figure 2: XML IntelliSense is available in Visual Basic when you add an XSD schema to the project.
Figure 3: Inspecting the result of grouping by position. Each result has a key and a collection of items. Each item in the group has the same key value.

Creating GeoRSS for the Virtual Earth Map Control

Until now the sample app has consumed XML data from the Windows Live Expo service. Now I want to generate some XML. The sample application uses a Virtual Earth map control to display the locations of the items returned from Windows Live Expo. After the map control is created on the Web page, it requests the pushpin locations from the server. The server returns the locations in an RSS feed that conforms to the GeoRSS specification. I’ll use LINQ to XML to create the GeoRSS XML.

One of the reasons why Microsoft created a new XML API was because the W3C-based XmlDocument and XMLElement in System.Xml cannot be functionally created. The System.XML API uses a factory-based pattern instead which, unfortunately, requires a factory context for creating the elements and it needs multiple statements for constructing the XML. With LINQ you need to do everything within the context of a single LINQ expression.

One of the reasons why Microsoft created a new XML API was because the W3C-based XmlDocument and XMLElement in System.Xml cannot be functionally created.

The constructor for XElement, shown below, takes a variable number of arguments.

XElement(XName name, params object[] content);

Because each constructor can take as its argument the constructor for its content, you can create the entire XML document or fragment within a single expression.

In the LINQ query, each item from the Windows Live Expo data is converted to a GeoRSS item. The transformation is simple and summarized in Table 2.

Listing 1 shows the C# code for the transformation.

With the items converted, the Virtual Earth map control requests the full GeoRSS XML by embedding the items inside of the RSS and channel elements. The server returns data to the map control via a separate aspx page.

C#

var geoRss =
  new XElement("rss",
    new XAttribute("version", "2.0"),
    new XElement("channel",
      new XElement("title",
        "Expo Live Result Locations"),
      new XElement("link", cmd),
      geoItems
    )
  );

This code correctly creates the GeoRSS XML but the XML may not serialize exactly the way you want it to look. While LINQ to XML names are always fully qualified, they do not have a prefix. If you care about the prefix assigned to a name then you need to create a namespace attribute that defines the prefix. Without a namespace declaration, the serializer may create a name with a default namespace or in some cases, with a synthesized prefix. In this case, I want the prefix for the geo namespace to be “geo”. To ensure the prefix is generated correctly. I’ll add the following code to the rss element.

new XAttribute(XNamespace.Xmlns + "geo",
geo.NamespaceName),

Using Grouping

At this point I thought I was done with the application. The ASP.NET ListView control displayed the items and the Virtual Earth map control displayed pushpins showing each item’s location. When I counted the pushpins, however, there weren’t enough.

The problem is that the position information for each item is the latitude and longitude of the item’s postal code. Two items with the same postal have the same position so the map control only displays one pushpin. Rather than generate multiple overlapping pushpins, I need to generate one pushpin and concatenate the titles and descriptions from each of the items at the same location. I can use LINQ grouping to solve this problem.

Grouping has two parts: an expression for the objects to put in the group and an expression for the key to partition the objects. In the query, I want to put the RSS items in the group and I want to partition on unique latitude and longitude values. Getting the items for the group is easy. That is just the item variable from the “from” clause. The key has to be a single expression but I have two values. To solve this I’ll use a new anonymous type with two members, latitude and longitude. I’ll then assign a name for this group so that I can access it in the select clause.

C#

var positions =
  from item in
    d.Elements("rss").
      Elements("channel").
        Elements("item")
  group item by
    new
    {
      latitude = (string)item.
        Element(expo + "location").
          Element(expo + "latitude"),
      longitude = (string)item.
        Element(expo + "location").
          Element(expo + "longitude")
    } into g
  select new { position = g.Key, data = g };

The select clause then returns the group key with the latitude and longitude and the items at that location. See Figure 3 to inspect the group query result in Visual Studio. The rest of the code to merge the titles and descriptions is in the sample application.

Visual Basic XML Literals

Complementary to XML properties, are XML literals. With XML literals you can create XML documents, elements, and fragments within a Visual Basic program using XML syntax. The code snippet below shows code similar to the C# example above to create the GeoRSS items in Visual Basic.

Visual Basic

Dim geoItems = _
  From item In d.<rss>.<channel>.<item> _
  Select _
    <item>
      <title><%= item.<title>.Value %></title>
      <description><%= _
     item.<description>.Value %>
      </description>
      <geo:lat><%= item.<expo:location>. _
      <expo:latitude>.Value %></geo:lat>
      <geo:long><%= _
    item.<expo:location>. <expo:longitude>.Value _
      %></geo:long>
      </item>

The XML literal compiles to calls to LINQ to XML API so the type of the geoItems variable is XElement. In addition to XML 1.0 syntax, XML literals support embedded Visual Basic expressions. You use embedded expressions to programmatically create content within an XML literal. They use an ASP.NET-like syntax and can contain any valid Visual Basic expression.

Because the XML literal is processed and understood by the Visual Basic compiler, you get additional support within the IDE when using XML literals. For example, the IDE automatically inserts the end element when starting a new element and will auto correct the end element name when the start element name is changed. In addition, features such as outlining, renaming, and find all references all work as expected with XML literals.

Conclusion

XML is everywhere on the Web and applications need to be able to easily produce and consume XML. While this article barely scratches the surface of the LINQ to XML APIs, you can see how easy it is to integrate data from Web services using the .NET Framework 3.5. LINQ to XML vastly simplifies querying and constructing XML. Visual Basic takes XML programming one level higher with XML properties, XML literals, and XSD-driven IntelliSense.