Evjen c10.tex V2 - 01/28/2008 2:13pm Page 527
Chapter 10: Working with XML and LINQ to XML
namespaceMgr.AddNamespace("b", "")
’All books whose price is not greater than 10.00
For Each node As XPathNavigator In nav.Select( _
"//b:book[not(b:price[. > 10.00])]/b:price", namespaceMgr)
Dim price As Decimal = _
CType(node.ValueAs(GetType(Decimal)), Decimal)
Response.Write(String.Format("Price is {0}<BR/>", _
price))
Next
C#
//Load document
string booksFile = Server.MapPath("books.xml");
XPathDocument document = new XPathDocument(booksFile);
XPathNavigator nav = document.CreateNavigator();
//Add a namespace prefix that can be used in the XPath expression
XmlNamespaceManager namespaceMgr = new XmlNamespaceManager(nav.NameTable);
namespaceMgr.AddNamespace("b", "");
//All books whose price is not greater than 10.00
foreach(XPathNavigator node in
nav.Select("//b:book[not(b:price[. > 10.00])]/b:price",
namespaceMgr))
{
Decimal price = (decimal)node.ValueAs(typeof(decimal));
Response.Write(String.Format("Price is {0}<BR/>",
price));
}
If you then want to modify the underlying XML nodes, in the form of an
XPathNavigator
,youwould
use an XmlDocument instead of an XPathDocument. Your XPath expression evaluation may slow you
down, but you will gain the capability to edit. Beware of this tradeoff in performance. Most often,
you will want to use the read-only XPathDocument whenever possible. Listing 10-13 illustrates this
change with the new or changed portions appearing in gray. Additionally, now that the document is
editable, the price is increased 20 percent.
Listing 10-13: Querying and editing XML with XmlDocument and XPathNodeIterator
VB
’Load document
Dim booksFile As String = Server.MapPath("books.xml")
Dim document As New XmlDocument()
document.Load(booksFile)
Dim nav As XPathNavigator = document.CreateNavigator()
’Add a namespace prefix that can be used in the XPath expression
Dim namespaceMgr As New XmlNamespaceManager(nav.NameTable)
namespaceMgr.AddNamespace("b", "")
’All books whose price is not greater than 10.00
For Each node As XPathNavigator In nav.Select( _
527
Evjen c10.tex V2 - 01/28/2008 2:13pm Page 528
Chapter 10: Working with XML and LINQ to XML
"//b:book[not(b:price[. > 10.00])]/b:price", namespaceMgr)
Dim price As Decimal = CType(node.ValueAs(GetType(Decimal)), Decimal)
node.SetTypedValue(price * CDec(1.2))
Response.Write(String.Format("Price raised from {0} to {1}<BR/>", _
price, _
CType(node.ValueAs(GetType(Decimal)), Decimal)))
Next
C#
//Load document
string booksFile = Server.MapPath("books.xml");
XmlDocument document = new XmlDocument();
document.Load(booksFile);
XPathNavigator nav = document.CreateNavigator();
//Add a namespace prefix that can be used in the XPath expression
XmlNamespaceManager namespaceMgr = new XmlNamespaceManager(nav.NameTable);
namespaceMgr.AddNamespace("b", "");
//All books whose price is not greater than 10.00
foreach(XPathNavigator node in
nav.Select("//b:book[not(b:price[. > 10.00])]/b:price",
namespaceMgr))
{
Decimal price = (decimal)node.ValueAs(typeof(decimal));
node.SetTypedValue(price * 1.2M);
Response.Write(String.Format("Price inflated raised from {0} to {1}<BR/>",
price,
node.ValueAs(typeof(decimal))));
}
Listing 10-13 changes the
XPathDocument
to an
XmlDocument
, and adds a call to
XPathNavigator
.SetTypedValue
to update the price of the document in memory. The resulting document could then
be persisted to storage as needed. If
SetTypedValue
was instead called on the
XPathNavigator
that was
returned by
XPathDocument
,a
NotSupportedException
would be thrown as the
XPathDocument
is read-only.
The
Books.xml
document loaded from disk uses
as its default namespace.
Because the
Books.xsd
XML Schema is associated with the
Books.xml
document, and it assigns the
default namespace to be
, the XPath must know how to resolve that names-
pace. Otherwise, you cannot determine if an XPath expression with the word
book
in it refers to a book
from this namespace or another book entirely. An
XmlNamespaceManager
is created, and
b
is arbitrarily
used as the namespace prefix for the XPath expression.
Namespace resolution can be very confusing because it is easy to assume that your XML file is all alone
in the world and that specifying a node named
book
is specific enough to enable the system to find it.
However, remember that your XML documents should be thought of as living among all the XML in the
world — this makes providing a qualified namespace all the more important. The
XmlNamespaceManager
in Listing 10-12 is passed into the call to
SelectNodes
in order to associate the prefix with the appropriate
namespace. Remember, the namespace is unique, not the prefix; the prefix is simply a convenience acting
as an alias to the longer namespace. If you find that you’re having trouble getting an XPath expression to
528
Evjen c10.tex V2 - 01/28/2008 2:13pm Page 529
Chapter 10: Working with XML and LINQ to XML
work and no nodes are being returned, find out if your source XML has a namespace specified and that
it matches up with a namespace in your XPath.
Using XPath with XDocuments in LINQ for XML
You can use XPath against an
XDocument
object by adding a reference to the
System.Xml.XPath
namespace via a using or Imports statement. Adding this reference adds new extension methods to the
XDocument
like
CreateNavigator
get to an
XPathNavigator
and the very useful XPathSelectElements.
XPathSelectElements is similar to the
SelectNodes
and
SelectSingleNode
methods of the
System.Xml
.XmlDocument
. These extension methods are part of the ‘‘bridge classes’’ that provide smooth integration
between
System.Xml
and
System.Xml.Linq
.
Listing 10-12q: Querying XDocuments with XPath Expressions
VB
Dim booksFile As String = Server.MapPath("books.xml")
Dim document As XDocument = XDocument.Load(booksFile)
’Add a namespace prefix that can be used in the XPath expression
Dim namespaceMgr As New XmlNamespaceManager(New NameTable())
namespaceMgr.AddNamespace("b", "")
’All books whose price is not greater than 10.00
Dim nodes = document.XPathSelectElements(
"//b:book[not(b:price[. > 10.00])]/b:price", namespaceMgr)
For Each node In nodes
Response.Write(node.Value + "<BR/>")
Next
C#
//Load document
string booksFile = Server.MapPath("books.xml");
XDocument document = XDocument.Load(booksFile);
//Add a namespace prefix that can be used in the XPath expression.
// Note the need for a NameTable. It could be new or come from elsewhere.
XmlNamespaceManager namespaceMgr = new XmlNamespaceManager(new NameTable());
namespaceMgr.AddNamespace("b", "");
var nodes = document.XPathSelectElements(
"//b:book[not(b:price[. > 10.00])]/b:price",namespaceMgr);
//All books whose price is not greater than 10.00
foreach (var node in nodes)
{
Response.Write(node.Value + "<BR/>");
}
Notice that the added method in Listing 10-12q,
XPathSelectElements
, still requires an
IXmlNames-
paceResolver
, so we create a new
NameTable
and map the namespaces and prefixes explicitly via
XmlNamespaceManager
. When using XElements and simple queries, you’re better off using LINQ to XML
and the new XElement-specific methods such as
Elements()
and
Descendants()
rather than XPath.
529
Evjen c10.tex V2 - 01/28/2008 2:13pm Page 530
Chapter 10: Working with XML and LINQ to XML
DataSets
The
System.Data
namespace and
System.Xml
namespace have started mingling their functionality for
some time. DataSets are a good example of how relational data and XML data meet in a hybrid class
library. During the COM and XML heyday, the ADO 2.5 recordset sported the capability to persist as
XML. The dramatic inclusion of XML functionality in a class library focused entirely on manipulation
of relational data was a boon for developer productivity. XML could be pulled out of SQL Server and
manipulated.
Persisting DataSets to XML
Classes within
System.Data
use
XmlWriter
and
XmlReader
in a number of places. Now that you’re more
familiar with
System.Xml
concepts, be sure to take note of the method overloads provided by the classes
within
System.Data
. For example, the
DataSet.WriteXml
method has four overloads, one of which
takes in
XmlWriter
. Most of the methods with
System.Data
are very pluggable with the classes from
System.Xml
. Listing 10-14 shows another way to retrieve the XML from relational data by loading a
DataSet from a SQL command and writing it directly to the browser with the
Response
object’s
OutputStream
property using
DataSet.WriteXml
.
Listing 10-14: Extracting XML from a SQL Server with System.Data.DataSet
VB
Dim connStr As String = "database=Northwind;Data Source=localhost; " _
& "User id=sa;pwd=wrox"
Using conn As New SqlConnection(connStr)
Dim command As New SqlCommand("select * from customers", conn)
conn.Open()
Dim ds As New DataSet()
ds.DataSetName = "Customers"
ds.Load(command.ExecuteReader(), LoadOption.OverwriteChanges, "Customer")
Response.ContentType = "text/xml"
ds.WriteXml(Response.OutputStream)
End Using
C#
string connStr = "database=Northwind;Data Source=localhost;User id=sa;pwd=wrox";
using (SqlConnection conn = new SqlConnection(connStr))
{
SqlCommand command = new SqlCommand("select * from customers", conn);
conn.Open();
DataSet ds = new DataSet();
ds.DataSetName = "Customers";
ds.Load(command.ExecuteReader(), LoadOption.OverwriteChanges, "Customer");
Response.ContentType = "text/xml";
ds.WriteXml(Response.OutputStream);
}
DataSets have a fairly fixed format, as illustrated in this example. The root node of the document is
Customers
, which corresponds to the
DataSetName
property. DataSets contain one or more named
DataTable
objects, and the names of these
DataTables
define the wrapper element — in this case,
Customer
. The name of the
DataTable
is passed into the
load
method of the DataSet. The correlation
530
Evjen c10.tex V2 - 01/28/2008 2:13pm Page 531
Chapter 10: Working with XML and LINQ to XML
between the DataSet’s name,
DataTable
’s name, and the resulting XML is not obvious when using
DataSets. The resulting XML is shown in the browser in Figure 10-4.
Figure 10-4
DataSets present a data model that is very different from the XML way of thinking about data. Much
of the XML-style of thinking revolves around the InfoSet or the DOM, whereas DataSets are row- and
column-based. The
XmlDataDocument
is an attempt to present these two ways of thinking into one
relatively unified model.
XmlDataDocument
Although DataSets have their own relatively inflexible format for using XML, the
XmlDocument
class does
not. In order to bridge this gap, an unusual hybrid object, the
XmlDataDocument
, is introduced. This object
maintains the full fidelity of all the XML structure and allows you to access XML via the
XmlDocument
API without losing the flexibility of a relational API. An
XmlDataDocument
contains a DataSet of its own
and can be called DataSet-aware. Its internal DataSet offers a relational view of the XML data. Any data
contained within the XML data document that does not map into the relational view is not lost, but
becomes available to the DataSet’s APIs.
531
Evjen c10.tex V2 - 01/28/2008 2:13pm Page 532
Chapter 10: Working with XML and LINQ to XML
The
XmlDataDocument
is a constructor that takes a DataSet as a parameter. Any changes made to the
XmlDataDocument
are reflected in the DataSet and vice versa.
Now take the DataSet loaded in Listing 10-14 and manipulate the data with the
XmlDataDocument
and
DOM APIs you’re familiar with. Next, jump back into the world of
System.Data
and see that the DataSets
underlying DataRows have been updated with the new data, as shown in Listing 10-15.
Listing 10-15: Changing DataSets using the DOM APIs from XmlDataDocument
VB
Dim connStr As String = "database=Northwind;Data Source=localhost; " _
& "User id=sa;pwd=wrox"
Using conn As New SqlConnection(connStr)
Dim command As New SqlCommand("select * from customers", conn)
conn.Open()
Dim ds As New DataSet()
ds.DataSetName = "Customers"
ds.Load(command.ExecuteReader(), LoadOption.OverwriteChanges, "Customer")
’Response.ContentType = "text/xml"
’ds.WriteXml(Response.OutputStream)
’Added in Listing 10-15
Dim doc As New XmlDataDocument(ds)
doc.DataSet.EnforceConstraints = False
Dim node As XmlNode = _
doc.SelectSingleNode("//Customer[CustomerID = ’ANATR’]/ContactTitle")
node.InnerText = "Boss"
doc.DataSet.EnforceConstraints = True
Dim dr As DataRow = doc.GetRowFromElement(CType(node.ParentNode, XmlElement))
Response.Write(dr("ContactName").ToString() & " is the ")
Response.Write(dr("ContactTitle").ToString())
End Using
C#
string connStr = "database=Northwind;Data Source=localhost; "
+ "User id=sa;pwd=wrox";
using (SqlConnection conn = new SqlConnection(connStr))
{
SqlCommand command = new SqlCommand("select * from customers", conn);
conn.Open();
DataSet ds = new DataSet();
ds.DataSetName = "Customers";
ds.Load(command.ExecuteReader(), LoadOption.OverwriteChanges,"Customer");
//Response.ContentType = "text/xml";
//ds.WriteXml(Response.OutputStream);
//Added in Listing 10-15
XmlDataDocument doc = new XmlDataDocument(ds);
doc.DataSet.EnforceConstraints = false;
XmlNode node = doc.SelectSingleNode(@"//Customer[CustomerID
= ’ANATR’]/ContactTitle");
532
Evjen c10.tex V2 - 01/28/2008 2:13pm Page 533
Chapter 10: Working with XML and LINQ to XML
node.InnerText = "Boss";
doc.DataSet.EnforceConstraints = true;
DataRow dr = doc.GetRowFromElement((XmlElement)node.ParentNode);
Response.Write(dr["ContactName"].ToString() + " is the ");
Response.Write(dr["ContactTitle"].ToString());
}
Listing 10-15 extends Listing 10-14 by first commenting out changing the HTTP
ContentType
and the call
to
DataSet.WriteXml
. After the DataSet is loaded from the database, it is passed to the
XmlDataDocument
constructor. At this point, the
XmlDataDocument
and the DataSet refer to the same set of information.
The
EnforceConstraints
property of the DataSet is set to
false
to allow changes to the DataSet. When
EnforceConstraints
is later set to
true
, if any constraint rules were broken, an exception is thrown.
An
XPath
expression is passed to the DOM method
SelectSingleNode
, selecting the
ContactTitle
node of a particular customer, and its text is changed to
Boss
. Then by calling
GetRowFromElement
on
the
XmlDataDocument
, the context jumps from the world of the
XmlDocument
back to the world of the
DataSet. Column names are passed into the
indexing
property of the returned
DataRow
,andtheoutput
is shown in this line:
Ana Trujillo is the Boss
The data is loaded from the SQL server and then manipulated and edited with
XmlDocument
-style
methods; a string is then built using a
DataRow
from the underlying DataSet.
XML is clearly more than just angle brackets. XML data can come from files, from databases, from
information sets like the DataSet object, and certainly from the Web. Today, however, a considerable
amount of data is stored in XML format, so a specific data source control has been added to ASP.NET 2.0
just for retrieving and working with XML data.
The XmlDataSource Control
The
XmlDataSource
control enables you to connect to your XML data and to use this data with any of
the ASP.NET data-bound controls. Just like the
SqlDataSource
and the
AccessDataSource
controls, the
XmlDataSource
control also enables you not only to retrieve data, but also to insert, delete, and update
data items.
One unfortunate caveat of the
XmlDataSource
is that its
XPath
attribute does not
support documents that use namespace qualification. Examples in this chapter use
the
Books.xml
file with a default namespace of
.Itis
very common for XML files to use multiple namespaces, including a default
namespace. As you learned when you created an
XPathDocument
and queried it with
XPath, the namespace in which an element existsisveryimportant.Theregrettable
reality is, there is no way to use a namespace qualified XPath expression or to make
the XmlDataSource Control aware of a list of prefix/namespace pairs via the
XmlNamespaceManager
class. However, the
XPath
function used in the
ItemTemplate
of the templated
DataList
control can take a
XmlNamespaceManager
as its second
parameter and query XML returned from the
XmlDataSource
—aslongasthe
control does not include an
XPath
attribute with namespace qualification or you can
533
Evjen c10.tex V2 - 01/28/2008 2:13pm Page 534
Chapter 10: Working with XML and LINQ to XML
just omit it all together. That said, in order for these examples to work, you must
remove the namespaces from your source XML and use XPath queries that include
no namespace qualification, as shown in Listing 10-16.
You can use a
DataList
control or any DataBinding-aware control and connect to an <
asp:
XmlDataSource
> control. The technique for binding a control directly to the
Books.xml
file is illustrated
in Listing 10-16.
Listing 10-16: Using a DataList control to display XML content
<%@ Page Language="VB" AutoEventWireup="false"
CodeFile="Default.aspx.vb" Inherits="Default_aspx" %>
<html xmlns=" >
<head id="Head1" runat="server">
<title>XmlDataSource</title>
</head>
<body>
<form id="form1" runat="server">
<asp:datalist id="DataList1" DataSourceID="XmlDataSource1" runat="server">
<ItemTemplate>
<p><b><%# XPath("author/first-name") %>
<%# XPath("author/last-name")%></b>
wrote <%# XPath("title") %></p>
</ItemTemplate>
</asp:datalist>
<asp:xmldatasource id="XmlDataSource1" runat="server"
datafile="~/Books.xml"
xpath="//bookstore/book"/>
</form>
</body>
</html>
This is a simple example, but it shows you the ease of using the
XmlDataSource
control. You should focus
on two attributes in this example. The first is the
DataFile
attribute. This attribute points to the location
of the XML file. Because the file resides in the root directory of the application, it is simply
∼
/Books.xml
.
The next attribute included in the
XmlDataSource
control is the
XPath
attribute. The
XmlDataSource
control uses the
XPath
attribute for the filtering of XML data. In this case, the
XmlDataSource
control
is taking everything within the
<
book
> set of elements. The value
//bookstore/book
means that the
XmlDataSource
control navigates to the <
bookstore
> element and then to the <
book
> element within
the specified XML file and returns a list of all books.
The
DataList
control then must specify its
DataSourceID
as the
XmlDataSource
control. In the
<
ItemTemplate
> section of the
DataList
control, you can retrieve specific values from the XML file by
using XPath commands within the template. The XPath commands filter the data from the XML file.
The first value retrieved is an element attribute (
author/first-name
) that is contained in the <
book
>
element. If you are retrieving an attribute of an element, you preface the name of the attribute with an
at (
@
) symbol. The next two XPath commands get the last name and the title of the book. Remember
534
Evjen c10.tex V2 - 01/28/2008 2:13pm Page 535
Chapter 10: Working with XML and LINQ to XML
to separate nodes with a forward slash (
/
). When run in the browser, this code produces the results
illustrated in the following list:
Benjamin Franklin wrote The Autobiography of Benjamin Franklin
Herman Melville wrote The Confidence Man
Sidas Plato wrote The Gorgias
Note that if you wrote the actual code, this entire exercise would be done entirely in the ASPX page itself!
Besides working from static XML files such as the
Books.xml
file shown earlier, the
XmlDataSource
control has the capability to work from dynamic, URL-accessible XML files. One popular XML format
that is pervasive on the Internet today is the weblog.Theseblogs, or personal diaries, can be viewed either
in the browser, through an RSS-aggregator, or as pure XML.
In Figure 10-5, you can see the XML from my blog’s RSS feed. I’ve saved the XML to a local file and
removed a stylesheet so I can see what the XML looks like when viewed directly in the browser. (You
can find a lot of blogs to play with for this example at
weblogs.asp.net.
)
Figure 10-5
535
Evjen c10.tex V2 - 01/28/2008 2:13pm Page 536
Chapter 10: Working with XML and LINQ to XML
Now that you know the location of the XML from the blog, you can use this XML with the
XmlDataSource
control and display some of the results in a
DataList
control.Thecodeforthisexampleisshownin
Listing 10-17.
Listing 10-17: Displaying an XML RSS blog feed
<%@ Page Language="VB"%>
<html xmlns=" >
<head runat="server">
<title>XmlDataSource</title>
</head>
<body>
<form id="form1" runat="server">
<asp:DataList ID="DataList1" Runat="server" DataSourceID="XmlDataSource1">
<HeaderTemplate>
<table border="1" cellpadding="3">
</HeaderTemplate>
<ItemTemplate>
<tr><td><b><%# XPath("title") %></b><br />
<i><%# XPath("pubDate") %></i><br />
<%# XPath("description") %></td></tr>
</ItemTemplate>
<AlternatingItemTemplate>
<tr bgcolor="LightGrey"><td><b><%# XPath("title") %></b><br />
<i><%# XPath("pubDate") %></i><br />
<%# XPath("description") %></td></tr>
</AlternatingItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:DataList>
<asp:XmlDataSource ID="XmlDataSource1" Runat="server"
DataFile=" />XPath="rss/channel/item">
</asp:XmlDataSource>
</form>
</body>
</html>
Looking at the code in Listing 10-17, you can see that the
DataFile
points to a URL where the XML
is retrieved. The
XPath
property filters and returns all the <
item
> elements from the RSS feed. The
DataList
control creates an HTML table and pulls out specific data elements from the RSS feed, such as
the
<
title
>, <
pubDate
>,and<
description
> elements.
Running this page in the browser, you get something similar to the results shown in Figure 10-6.
This approach also works with XML Web services, even ones for which you can pass in parameters using
HTTP-GET. You just set up the
DataFile
property value in the following manner:
DataFile=" />536