Sharpening Your Axis with Visual Basic 9

isual Basic 9 has a new set of language features that allows developers to work with XML in a much more productive way using a new API called “LINQ to XML.”

LINQ stands for “Language Integrated Query,” and it lets you write queries for objects, databases, and XML in a standard way. Visual Basic provides deep support for LINQ to XML through XML literals and XML axis properties. These features let you use a familiar, convenient syntax for working with XML in your Visual Basic code. LINQ to XML is a new, in-memory XML programming API specifically designed to leverage the LINQ framework. Even though you can call the LINQ APIs directly, only Visual Basic allows you to declare XML literals and directly access XML axis properties. This article will help you master these new Visual Basic XML features.

XML Literals and Embedded Expressions
Traditionally, when working with XML in code you would use the XML Document Object Model (XML DOM), and call its API to manipulate objects representing the structure of your XML. This API is document-centric, which does not lend itself well to creating XML with LINQ. In addition it uses the XPath language, which is unnecessary when using LINQ?as you will soon see. Another way to work with XML would be to drop out of Visual Basic code altogether and manipulate XML using XSLT, XQuery, or XPath directly.

With the release of the .NET Framework 3.5, the LINQ to XML API allows developers to work with XML in a much more intuitive way, taking a top-down, element-centric approach. Additionally, in Visual Basic 9 (VB9) you can use XML literals to embed XML directly into your code and bring an even more WYSIWYG approach to XML programming. For example, the following code is perfectly legal in VB9:

   Dim myXml =                                   This is XML               

The runtime infers that the myXml variable is an XDocument object in this case (XDocument is a class in the LINQ to XML API that, in many cases, performs much better than the XML DOM). Type inference is another new feature of VB9 that supports LINQ. The compiler can infer the types of local variables by evaluating the right-hand side of the assignment. In previous versions of Visual Basic, the preceding statement would produce a type of Object; but with the new Option Infer flag set to ON in VB9, the statement above is now the same as declaring the XDocument type explicitly, so your code still benefits from compile-time checks. As you can see there is no conceptual barrier between the code you write and the XML you’re trying to express when using XML literals in VB9.

In the editor, you can see XML syntax coloring, which indicates that Visual Basic recognizes this XML literal as a supported data type. As you type the beginning tag of the element, Visual Basic will automatically insert the end tag and format the XML. If you change the name of the element’s beginning tag, Visual Basic updates the end tag to match. Also note that underscores are not necessary in multiline XML literals.

XML literals get much more interesting when you use them with embedded expressions. Embedded expressions let you write any Visual Basic expression and have it evaluated directly in the literal. These expressions are compiled code, not script, so you benefit from the same compile-time syntax checks and editor experience you’re accustomed to when writing programs in Visual Basic. The syntax you use is <%= expression %>. For example, using the simple literal above you could embed an expression in the element to write out the current date and time inside the element instead:

   Dim myXml =                    <%= Now() %>               

Notice that this example doesn’t start with the XML declaration; instead VB9 infers this to be an XElement object, the fundamental class in the LINQ to XML API. XML trees are made up of these XElement objects. This is just a simple example; there’s no practical limit to the number of embedded expressions you can include in XML literals. You can also nest embedded expressions any number of levels deep.

Here’s an example of a LINQ query that queries the files in the current directory and creates an XML output document using XML literals and embedded expressions.

   Dim myXml = _         <%= From FileName In _             My.Computer.FileSystem.GetFiles(CurDir()) _             Let File = _             My.Computer.FileSystem.GetFileInfo(FileName) _             Select _             >                                  <%= File.Name %>                                                 <%= File.CreationTime %>                                                 <%= File.LastWriteTime %>                                                 <%= File.DirectoryName %>                             %>      myXml.Save("MyFiles.xml")

The preceding code produces XML something like this:

               customers.html                2007-10-07T15:57:00.2072384-08:00                       2007-10-22T14:46:48.6157792-07:00              C:	emp                 orders.xml                2007-10-07T15:57:00.2773392-08:00                       2007-10-27T12:01:00.3549584-07:00              C:	emp                 plants.xml                2007-10-07T15:57:00.147152-08:00                       2007-10-18T18:25:07.2607664-07:00              C:	emp        

Notice in the example that you first start writing an XML literal and then embed a LINQ query that selects a collection of XElements to create the document. You can nest embedded expressions this way to create very complex documents in a much more intuitive way. This example creates the XML from the top down?just as you would read it. This feature lets you be much more productive using the LINQ to XML API than its predecessor, the XML DOM, which forced you to think in terms of its API instead of the actual XML you’re trying to express.

Editor’s Note: This article was first published in the July/August 2008 issue of CoDe Magazine, and is reprinted here by permission.

XML Axis Properties
You use XML axis properties to navigate the XML. There are three axis properties; the descendant axis, the child axis, and the attribute axis. You use the descendant axis property to access all XML elements that have a specified name that are contained in a specified XML element, regardless of the level. You use the child axis property to access just the XML child elements in a given XML element. The child axis performs much better than the descendant axis, and you should use the child axis unless you really need to search for the element name in all levels of the tree. Finally, use the attribute axis property to access the attributes in an XML element.

For example, taking the files example above, you can access all the elements using the descendant axis, which uses a three-dot notation, by simply writing myxml…. If you wanted to transform the above XML, selecting just the name elements to create a new XElement, you would write:

   Dim names =                    <%= myXml... %>                  names.Save("Names.xml")

That would produce this transformed XML:

        customers.html     orders.xml     plants.xml   

You could produce the same simple transformation using child axis properties instead, using a single-dot notation, walking down the hierarchy as shown below:

   Dim names =                    <%= myXml... %>               

To return the contents of the XElement instead of the object itself you can use the .Value property. For example:

   Dim names = _                     <%= myXml....Value %>          

This results in this transformed XML:

        customers.html     orders.xml     plants.xml   

To access the isReadonly attribute in the example you’d use the attribute axis property, which uses an ampersand (@) notation. Suppose you want to transform this document and select only the elements where isReadonly is false:

   Dim files =                <%= From File In myXML... _   Where [email protected] = "false" _   Select File %>               

This would produce the following result:

               orders.xml                2007-10-07T15:57:00.2773392-08:00                       2007-10-27T12:01:00.3549584-07:00              C:	emp                 plants.xml                2007-10-07T15:57:00.147152-08:00                       2007-10-18T18:25:07.2607664-07:00              C:	emp        

XML axis properties make it much easier to navigate XML, and also improve code readability. One thing to keep in mind when using axis properties is that unlike Visual Basic, XML is case-sensitive. Therefore the case of the axis property does matter. If you had accidentally written myXml… in the example instead, then it would have returned no elements, because there is no element in the XML, only lowercase elements. This is definitely something to watch out for but Visual Basic can help. To decrease the likelihood of making mistakes like this you can enable XML IntelliSense.

Enabling XML IntelliSense
Visual Basic can enable IntelliSense for XML elements and attributes defined in an XML schema (XSD). If you include a schema file in your project, the editor will add the applicable elements and attributes to the IntelliSense list of XML properties. To make it easier to infer and add XML schemas to your Visual Basic project, you can download the “XML to Schema” tool from the Visual Basic Developer Center (in the Downloads section).

The XML to Schema tool adds a New Item project template to the list of installed templates that you can use. When you add this item to your project, the XML to Schema wizard opens. The wizard can infer the XML schema set for all the XML sources used in your program, and it will automatically add the schema files to your project. You can specify the XML sources using file paths, URLs that return XML, or by pasting or typing an XML document directly into the wizard. The walkthrough provided on the Visual Basic Developer Center download page for the XML to Schema tool provides detailed instructions.

As an example, suppose you have an XML document that contains customer information. Using the XML to Schema tool you can infer the schema and add the resulting XSD file to your project in one step (see Figure 1). Now, when you access parts of the customers XML using XML axis properties you see IntelliSense based on the schema that was inferred from the XML (see Figure 2).

?
Figure 1. XML to Schema Tool: The XML to Schema tool can infer XML schemas from your input XML documents.
?
Figure 2. XML IntelliSense: After including the appropriate schemas in your project, Visual Basic displays XML IntelliSense.

Confidence Levels for XML IntelliSense
The first time you use XML properties on an XDocument or XElement object variable, the IntelliSense list will contain all the possible elements that can appear based on the information known to the IntelliSense engine through the schema. The icon near the entries in the IntelliSense list will have a red question mark to indicate that the exact XSD type is not known. After you select an item from the IntelliSense list, the next XML property you access in the context of that variable will be displayed in the IntelliSense list as an exact match based on the schema, indicated by a green check mark.

For example, Figure 2 demonstrates IntelliSense items displayed with a green checkmark, because it is clear that the elements listed are the only possible choices for child elements of the element.

In other words, when IntelliSense can identify a specific type from the schema, it will display possible child elements, attributes, or descendants with a high degree of confidence. However, in cases where IntelliSense is not able to identify a specific type from the schema, it will display an expanded list of possible elements and attributes, but with a low degree of confidence, indicated by a red question mark.

XDocument vs. XElement: Effect on XML IntelliSense
In the LINQ to XML API, when the variable type is XDocument, the variable is positioned outside the root element of the document so the IntelliSense list will contain all the root elements in the XML schemas that are part of the project. If the variable type is XElement it means that the element can be positioned on any element within the project’s XML schemas; therefore, the IntelliSense list will contain all the child elements of all the elements in these XML schemas.

Consider the example in Figure 3 where the variable doc is an XDocument created from an XML literal. The IntelliSense list contains all the root elements in the XML schemas in the project (this case displays only the element because there is only one schema). If you change the literal to be of type XElement (as shown in Figure 4) the XElement can be of any XSD type, so the IntelliSense box contains all the child elements of all the elements in the project’s XML schemas, in this case , and elements.

?
Figure 3. XDocument Object IntelliSense: With an XDocument object, the IntelliSense list contains the root elements from the XML schemas in the project.
?
Figure 4. XElement Object IntelliSense: With an XElement object, the IntelliSense list contains all the child elements from the XML schemas in the project.

One optimization that the IntelliSense engine makes is to look for the Load method when an XElement variable is created. The Load method is a shared method exposed by the XElement and XDocument classes that lets you specify the XML source location, be that a file on disk or a URL. If you initialize the variable using XElement.Load, the IntelliSense list will contain the child elements of only the root elements in the project’s XML schemas, and not all elements. This is because the XElement must be one of the schema root elements itself.

?
Figure 5. Optimizing Itellisense: Visual Studio optimizes the IntelliSense list when using XElement.Load().

For example, let’s assume that the myDoc.xml file contains the same XML document as the literal in Figure 4. In Figure 5 the IntelliSense list contains only the element because the XElement doc is already the root element .

Importing XML Namespaces
When you work with multiple XSD schemas and XML that defines namespaces you can import the XML namespaces at the top of your Visual Basic code files in the same way you import .NET framework namespaces. Microsoft expanded the Imports keyword so you can specify an XML namespace like this:

   Imports 

For example, suppose your XML declares an XML namespace:

                        Howard Snyder   ...

When you infer the schema it will contain a targetNamespace attribute:

              ...
?
Figure 6. Importing XML Namespaces: Import XML namespaces and then qualify the axis properties with the alias.

Now you can add the Imports statement above and qualify the axis properties with the XML namespace alias when you create, query or transform your XML (see Figure 6).

Importing XML namespaces has an effect on XML IntelliSense, causing it to match both the namespace prefixes and the local names are matched as you type. This offers greater productivity when coding, because instead of typing the prefix, then the colon, and then the local name, you can simply start typing the local name.

Figure 7 shows a simple example of how it works, starting with an input document and the applicable IntelliSense. If you just type the letter “n” in this example, then IntelliSense selects the customers:name entry right away (see Figure 8). If you type the letter “c” then IntelliSense selects customers:city instead (see Figure 9), and the IntelliSense list also contains the prefix customers and the category element. The next character that you type after the “c” determines which single entry IntelliSense will select.

?
Figure 7. IntelliSense and Namespace Prefixes: XML IntelliSense matches both the namespace prefixes and the local names as you type.
?
Figure 8. Matching a Unique Element: Typing just the letter “n” causes IntelliSense to match the customers:name entry.
?
Figure 9. Listing Ambiguous Entries: Typing just the letter “c” causes IntelliSense to list all the possible entries that start with “c.”

Working with Relational Data and XML using LINQ
Now that you’ve seen a foundation for working with XML in Visual Basic, here are some practical examples that show how to use the new features. In typical data-oriented applications developers work with XML as well as with relational data stored in a database. This walkthrough shows some examples of creating and consuming XML in VB9 when working with relational data.

Creating XML from Relational Data
One common requirement in modern applications is to create XML from data stored in a database. Visual Studio 2008 offers many ways to access database data, including the new O/R designer and LINQ to SQL, so you can easily connect and query your databases directly and then use the query results to create XML. To work with LINQ to SQL in Visual Studio 2008, right-click on your project, add a new item, and select the “LINQ to SQL Classes” template. The example in this section uses the Northwind database, so name the model Northwind.dbml.

?
Figure 10. Object/Relational Designer: The figure shows the O/R Designer in Visual Studio 2008 displaying the Customer class from the Northwind database.

Visual Studio creates a new model and opens the Object Relational Designer (O/R designer), which allows you to drag-and-drop tables from connected databases in the Server Explorer onto the design surface. After you have a connection to the Northwind database in Server Explorer, drag the Customers table onto the design surface. The O/R designer will automatically create a Customer class, and infer its properties from the table’s fields. It also sets up a DataContext, called “NorthwindDataContext” in this case, that you use to access the Northwind database (Figure 10). With those in place, you can write a LINQ query that selects all the customers located in the United States and their addresses from the Customers table, and (at the same time) format the results into an XML document containing the information.

   Dim db As New NorthwindDataContext   Dim customerUSA = _          <%= From Customer In db.Customers _   Where Customer.Country = "USA" _   Select >                                        <%= Customer.ContactName %>                                    
<%= Customer.Address %>
<%= Customer.City %> <%= Customer.PostalCode %>
%>
customerUSA.Save("Customers.xml")

Notice that this sample uses one LINQ query to create an XML document. Although this example uses LINQ to SQL to handle selecting the data from the database, you aren’t limited to getting the data in that manner. For example, if you already have a typed DataSet containing the customers, then can write the query using LINQ to DataSets instead, which performs a query against the in-memory, client-side DataSet.

   Dim NorthwindDataSet As New NorthwindDataSet   Dim ta As New _      NorthwindDataSetTableAdapters.CustomerTableAdapter   ta.Fill(NorthwindDataSet.Customers)   Dim customerUSA = _              <%= From Customer In NorthwindDataSet.Customers _        Where Customer.Country = "USA" _        Select >                                        <%= Customer.ContactName %>                                    
<%= Customer.Address %>
<%= Customer.City %> <%= Customer.PostalCode %>
%>
customerUSA.Save("Customers.xml")

Using either query, the resulting XML document will look something like this (only the first three customers are shown here):

                  Howard Snyder       
2732 Baker Blvd.
Eugene 97403
Yoshi Latimer
City Center Plaza 516 Main St.
Elgin 97827
John Steel
12 Orchestra Terrace
Walla Walla 99362
?
Figure 11. More Northwind Tables: The O/R Designer infers object associations from the database relationships.
. . .

As you can see it’s extremely easy to create XML from your relational data using XML literals and embedded expressions. And because you can easily nest embedded expressions, you can create much more complex documents in a single query. For example, what if you also want to include orders and order details for these customers? No problem. You can add Orders, Order Details and Products tables to the Northwind data model created by the O/R designer and then easily include those items in your query (see Figure 11).

When you add those new tables to the model, the O/R designer automatically sets up associations between the classes it creates by reading the database relations. Then you can write a query with nested embedded expressions to create more complex XML shown in Listing 1.

The code walks down the hierarchy of Customers, Orders, and then Order Details, creating XElements as it goes. Notice that, even though this XML document is

?
Figure 12. Complex Query: Here’s the output of Listing 1, shown in Internet Explorer.

complex, LINQ to XML still lets you take a natural top-down approach to writing the document-generation code. Also note that the aggregate query that calculates the order totals, which completely avoids any For loops. The query in Listing 1 outputs the document shown in Figure 12 (shortened for clarity and displayed in Internet Explorer).

You can create XML from multiple data sources as well, not just a single data source such as the database used in the examples so far. Because most modern systems interact with each other in some form of XML, the possibilities are endless. You can use LINQ to XML to easily create SOAP, XAML, HTML, and RSS.

For example, suppose you wanted to display all the customers on a map generated by Microsoft Virtual Earth? Virtual Earth allows you to pass it an RSS document containing items that specify their latitude and longitude, which it uses to create a map showing multiple locations. You can pass the data in a couple of different formats; one is the GeoRSS standard. All you would need to do is create the XML input file by obtaining the latitude and longitude from the addresses you have in your customers table and then pass this GeoRSS to Virtual Earth.

For this example you can grab the latitude and longitude of the customers in the United States using the service at http://geocoder.us. This service can return a set of coordinates from any U.S. address in a variety of formats, including REST-ful RDF. You can use this service in your LINQ query to create the GeoRSS from the Customers table. The first thing to do is to import the geo namespace at the top of your code file, because you’ll be using it to return the location information in the geo namespace from the XML returned from the geocoder.us service:

   Imports 

Now you can write a query to create the GeoRSS containing the customer data. Because the Northwind database contains mostly fictitious addresses you can change the addresses to real locations?or you can select just the customers living in Oregon (OR) because some of those entries have valid addresses.

   Dim geoRSS = _                 Northwind Customer Locations          <%= From Customer In db.Customers _   Let Desc = _       Customer.Address & ", " & Customer.City _   Let Address = _       Customer.Address & "," & Customer.PostalCode _   Where Customer.Country = "USA" _   AndAlso Customer.Region = region _   Select _                  <%= Customer.ContactName %>           <%= Desc %>           <%= GetGeoCode(Address).Descendants %>        %>          

In this query, we’re building up the GeoRSS and calling a user defined function called GetGeoCode that accepts an address, and returns the latitude and longitude corresponding to that address. Also notice that the query uses the Let keyword to create query variables for description and address, which are later used to build the elements. The GetGeoCode function returns an XElement containing the location if one was found. The XElement’s Descendants method is then called to place just the and nodes into the GeoRSS. Here’s the GetGeoCode method implementation:

   Function GetGeoCode(ByVal address As String) _      As XElement      Dim url = "http://geocoder.us/service/rest/?address=" & _         Server.UrlEncode(address)      Try         Dim geo = XElement.Load(url)         Return                   <%= geo.. %>                  <%= geo.. %>                      Catch ex As Exception         Return       End Try   End Function

Now you can pass the GeoRSS to Virtual Earth to create a map. For this example you can just create a simple ASP.NET application and save the GeoRSS above to a session variable. The default.aspx page (shown in Listing 2) contains the JavaScript code you need to send the GeoRSS to Virtual Earth, and a id=”myMap”> section that identifies the area to display the map on the page. Take a look at the Virtual Earth documentation for more information on the API.

The code behind for the default.aspx page simply checks whether the geoRSS query described above returned any elements. If so, it dynamically adds the code to call the GetMap JavaScript function in the body onload event.

   If geoRSS....Count > 0 Then      Session("georss") = geoRSS      Me.body.Attributes.Add("onload", _         String.Format("GetMap()"))   Else      Me.lblStatus.Visible = True      Session("georss") =    End If

The project contains another page called GeoRss.aspx, which simply returns the GeoRSS stored in the session variable that the JavaScript calls to get the content. It’s important that you set the ContentType property on the Response object to text/xml.

?
Figure 13. Sample Map Showing Customer Locations: By formatting the results of the LINQ to XML query as GeoRSS, and passing that to Virtual Earth, you can display the locations of customers on a map.
   Public Partial Class GeoRSS      Inherits System.Web.UI.Page      Protected Sub Page_Load( _         ByVal sender As Object, _         ByVal e As System.EventArgs) _         Handles Me.Load            Dim georss As XElement = _            CType(Session("georss"), XElement)         Response.ContentType = "text/xml"         Response.Write(georss.ToString())   End Sub   End Class

You can see the results in Figure 13.

The key takeaway here is that in one LINQ statement, you queried multiple data sources (the Northwind database and the geocoder.us service), to create a single XML document that conformed to the GeoRSS standard, and passed that to the Virtual Earth service to generate the map.

Creating Relational Data from XML
As you can imagine, it’s also very easy to create relational data by reading XML. You can easily navigate XML in Visual Basic using XML axis properties. For example, suppose you want to read an XML document called Customers.xml that contains customers in the United States?like the one you created in the first Northwind example. It’s a good idea to first add an XML namespace to the Customers.xml document. That will make it much easier if you need to work with multiple namespaces and schemas in the project later.

                  Howard Snyder       
2732 Baker Blvd.
Eugene 97403
. . .

Next, infer the schema in order to enable XML IntelliSense using the XML to Schema tool. Add a new item to the project, select the XML to Schema template, and name it Customers.xsd. Then specify the location of the Customers.xml by selecting “Add from file?” and browsing to the file location; then click OK. That will infer the schema and add it to your project all in one step.

Import the XML namespace at the top of your code file:

   Imports 

Now, when you write LINQ to XML queries over this customer document you will get full IntelliSense on the XML axis properties. For example, to select all the IDs and names of the customers that live in cities that start with the letter “P,” you would write:

   Dim custXML = XElement.Load("Customers.xml")   Dim CustomersLikeP = _      From Customer In custXML. _      Where Customer..Value Like "P*" _      Select [email protected], _         Customer..Value   For Each cust In CustomersLikeP      Console.WriteLine(cust.id & ": " & cust.name)   Next

This query produces a collection of anonymous types with two properties, id and name, created based on the names of the element and the attribute that you selected from the XML. Anonymous types are another new feature of VB9 that allow you to project unnamed types from LINQ queries. This allows you to use the results from your query immediately, without having to create a class to hold the results.

The next example reads the XML document and adds any customers that are not already in the Northwind database. You can write a single LINQ to XML query to do this. To select only the customers in the XML document where there is no corresponding customer in the database, the example uses the Group Join syntax to “outer join” the customers in the XML document to the customers in the database, selecting only those where the count is zero (meaning there is no corresponding row in the database). Additionally, the code selects a set of new LINQ to SQL Customer objects to create a collection of Customer objects that can be attached to the DataContext and automatically inserted into the database. Remember, the O/R designer created the Customer class when you added the Customers table to the LINQ to SQL model as described in the first Northwind example. Refer back to Figure 11 if you need to:

   Dim db As New NorthwindDataContext   Dim customersXML = XElement.Load("Customers.xml")   Dim newCustomers = _      From Cust In customersXML. _      Group Join CurrentCust In db.Customers _      On [email protected] Equals CurrentCust.CustomerID _      Into Count() _      Where Count = 0 _      Select New Customer With _        {.CustomerID = [email protected], _         .CompanyName = "New Company", _         .ContactName = Cust..Value, _         .Address = Cust..Value, _         .City = Cust..Value}   db.Customers.InsertAllOnSubmit(newCustomers)   db.SubmitChanges()

As you type this query into the editor you’ll get full IntelliSense on the customer XML axis properties. Note that the New Customer With {… syntax is another new feature in VB9 called object initializers that let you both create objects and assign their properties in one line of code. Also note that this code loads the document into an XElement, and finds the level where the elements are by using the child axis, which is faster than using the descendants axis. However, if you need to query documents where you do not know for sure on what level the element resides, you would want to use the descendants axis like this: customersXML….

The preceding query produces a collection of new Customer objects. You add this collection to the DataContext by calling the InsertAllOnSubmit method, and then insert the new customers into the database by calling SubmitChanges. For more information on LINQ to SQL, see the LINQ to SQL documentation on MSDN.

As you can see, it’s extremely easy to query XML using LINQ and XML axis properties, especially if you enable XML IntelliSense. The possibilities are rather endless.

Advanced Namespace Scenarios
Now that you’ve seen how to create, transform, and query XML using XML literals, embedded expressions, and axis properties, the rest of this article presents some more advanced scenarios around XML namespaces.

The Default XML Namespace
The default XML namespace can be extremely useful when creating an XML document using many functions and literal fragments. When using the default XML namespace you do not need to prefix the elements in all the literals; you can simply define the default namespace at the top of the code file and all the elements in that file will be in that namespace.

If you don’t define a default namespace, the default XML namespace in Visual Basic is the empty string. There are three places in your applications where you can override this default and define your own; inside the literal, in the Imports section at the top of the code file, or in the Imports section for the project. The important thing to remember when defining a default XML namespace is that the file and the project default namespaces apply to both the XML properties and XML literals but do not apply to attributes.

For example, the following Imports statement sets urn::myNameSpace as the default XML namespace for the file:

   Imports 

Now you can create an XML document in this namespace as follows:

   Dim doc =                                                                                            
?
Figure 14. Default Namespace Recognized: XML properties pick up the default namespace, so you’ll see applicable elements included in the IntelliSense list.

Because the XML properties also pick up this namespace you can observe that the applicable elements are included in the IntelliSense list (see Figure 14). You can also override the default namespace inside the XML literal itself. For example:

   Dim doc =                                                                     

That code produces the following XML document:

                       

To access the and elements using the XML properties, you’ll need to add another Imports statement and give the other namespace an alias. This is because you can declare only one default namespace at the file level:

   Imports 

You can see the result in Figure 15.

?
Figure 15. Imported Namespaces: XML IntelliSense displays Imported XML Namespace aliases.

Reify XML Namespace
You may encounter cases when you need to get the actual namespace that was declared in the Imports statement as an XNamespace object, another object in the LINQ to XML API. For example, suppose you need to create the name of the XML literal element using an embedded expression. The built-in function GetXmlNamespace makes this simple. For example, assume you have the following Imports declarations:

   Imports    Imports 

When you write this code:

   Console.WriteLine(GetXmlNamespace())   Console.WriteLine(GetXmlNamespace(ns))

It produces the following results:

   urn::myNamesapce   urn::AnotherDefaultNamespace

The next example uses this built-in function when calling the LINQ to XML Ancestors() method, which returns all the ancestor “{urn::AnotherDefaultNamespace}customer” elements of the order element:

   Dim customers = _      order.Ancestors( _      GetXmlNamespace(ns).GetName("customer"))

Bubbling and Removing Redundant Namespace Declarations

?
Figure 16. Bubbling and Removing Redundant Namespace Declarations: The figure shows the VB9 compiler’s decision tree for bubbling and removing redundant namespace declarations.

XML documents created from multiple functions and embedded LINQ queries typically have many redundant XML namespace declarations, because each element created on its own has to have the full namespace information contained within that element. However when this element becomes part of another element, the namespaces might be declared on the parent element, so they become redundant on the child elements. The VB9 compiler removes the redundant namespaces when the document gets assembled from XML literals during compilation and also at runtime. Figure 16 shows a flowchart of this process.

A few rules apply to this process. Only the global namespaces defined using the Imports statement, and local declarations that match global declarations get moved up to the parent element, creating the bubbling effect. In this case “match” means both the prefix and the namespace itself. Within a literal, namespaces will bubble up at compile time. However, if you add an element to another element by calling a function inside an embedded expression, the namespaces will bubble up at runtime.

The following examples show how this process works. Here’s a simple example of bubbling the namespace to the top level:

   Imports    ...   Dim doc =                                                                     

When you output doc, you’ll get the following results:

                       

In the next example the declaration already exists on the parent element. Note that because it’s the same prefix and same namespace, everything moves to the root element:

   Imports    ...   Dim doc2 =                                                                         

The outcome is that when you output doc2, you’ll get the same results as before:

                       

Local declarations do not bubble up unless the local declaration matches a global one. In this case you don’t declare globally:

   Imports    ...   Dim doc3 =                                                                         

So now when you output doc3, you’ll see this:

                       

The prefix and namespace must match to bubble up; if they don’t, then no bubbling occurs. Notice what happens when the declaration exists but with a different prefix:

   Imports    ...   Dim doc4 =                                                                         

You’ll end up with this result when you output doc4:

                       

Namespace bubbling also happens when working with embedded expressions. In the following example, the bubbling happens at compile time:

   Imports    ...   Dim doc5 =                 <%= From i In Enumerable.Range(1, 3) _                    Select                                          %>              

When you output doc5, you’ll see these results:

                                                         

Here’s an example of bubbling at runtime. In this case, the namespace gets bubbled from the elements at runtime because the compiler does not have enough information to remove them at compile time:

   Imports    ...   Dim doc6 =                   <%= GetElement() %>                 ...   Function GetElement() As XElement   Return _                  <%= From i In Enumerable.Range(1, 3) _               Select                                          %>          End Function

This results in the following output for doc6:

                                                                                       

It’s important to note that namespace bubbling at runtime hurts performance. To achieve optimal performance in this case, it’s better to return a single XElement from the function that is called within the embedded expression. When the function returns a single XElement, which itself wraps the query, the impact on performance is negligible because the bubbling and removing of redundant namespaces happens only once?for that element?instead of for each element that the query returns.

Tips and Tricks
Now that you’ve mastered XML programming in Visual Basic, here are some additional uses of LINQ to XML and XML literals that you may not have thought about that will probably save you some time.

Using XML Literals Instead of Multi-line Strings
One handy use of XML literals in Visual Basic is to replace your multi-line strings with XML literals for better readability. In Visual Basic, multi-line strings quickly become cumbersome because you have to concatenate string literals with your code and use underscores for readability. Here’s an example of old-style multi-line string programming:

   'Old way to work with multi-line strings   Dim oldWay = "this is a string" & vbCrLf & _                "with formatting" & vbCrLf & _                "and stuff" & vbCrLf & _                "look, underscores" & vbCrLf & _                "         tabs too"   MsgBox(oldWay)

With VB9’s built-in XML literals support, you can now easily write a bunch of text directly into the editor without having to use underscores:

   'Use XML literals to avoid underscores    ' and preserve formatting   Dim newWay As String =      this is a string     with formatting     and stuff     look, *no* underscores!!!     tabs too.Value   MsgBox(newWay)

The text formatting is preserved here as well. All you have to do is get the .Value of the XElement, which is the string literal. As you can see, this is much cleaner than what you’re used to in the first example. And if you still like to see your string literals in the default reddish color, you can easily change the color settings for VB XML literals by following the menu path Tools ? Options $rarr; Environment $rarr; Fonts and Colors, and then selecting “VB XML Text.” Set the custom color to RGB(163,21,21) to match old-style string literals. Here’s another example that builds an SQL query:

   'Use it anywhere you want better readability for    ' multi-line string literals   Dim query =    SELECT         Customers.*,         Orders.OrderDate    FROM         Customers    INNER JOIN Orders          ON Orders.CustomerID = Customers.CustomerID   WHERE Customers.CustomerID = @CustomerID              .Value   Dim cmd As New SqlClient.SqlCommand(query)   cmd.Parameters.AddWithValue("@CustomerID", id)

Text Merging
You can use XML literals to produce a lot more than just XML. In fact, you can easily create any text-based output using XML literals and embedded expressions. Using embedded expressions lets you to perform any kind of text merging in a much cleaner manner than string concatenation in code or using String.Format, especially as the size of the text increases. Here are some simple examples:

   'Simple text-merging example using embedded    ' expressions   Dim simple =       This is a simple text merge example:      Hello, <%= Environment.UserName %>.Value      MsgBox(simple)      Dim controls =       There are the following controls on this form:      <%= From item In Me.Controls _      Select item.ToString & vbCrLf %>      .Value   MsgBox(controls)

Listing 3 shows an example that uses a simple code generation pattern to generate a Customer.vb file that describes a Customer class by reading the schema. This is a good example of having to use the descendants axis to obtain all the elements no matter where they appear in the document.

Working with XML in VB9 via the LINQ to XML API using XML literals and XML axis properties completely eliminates the barriers between the code you write and the XML you’re trying to express. The new LINQ to XML API provides a much more intuitive approach to querying, transforming, and creating XML, and in many cases has better performance than its predecessor, the XML DOM. Say “Goodbye” to XPath, XQuery, and XSLT, and “Hello” to Visual Basic 9. For further reading, here are some additional resources you might find useful:

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

More From DevX

DevX is the leading provider of technical information, tools, and services for professionals developing corporate applications.

Join Our Newsletter

Subscribe to receive our latest blog posts directly in your inbox!