> 10.00]]/b:price", namespaceMgr Dim price As Decimal = _ CTypenode.ValueAsGetTypeDecimal, Decimal Response.WriteString.Format"Price is {0}", _ price Next C# //Load document string books
Trang 1namespaceMgr.AddNamespace("b", "http://example.books.com")
’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", "http://example.books.com");
//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 anXPathNavigator, you would
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", "http://example.books.com")
’All books whose price is not greater than 10.00
For Each node As XPathNavigator In nav.Select( _
Trang 2"//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", "http://example.books.com");
//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 theXPathDocumentto anXmlDocument, and adds a call toXPathNavigator
.SetTypedValueto update the price of the document in memory The resulting document could then
be persisted to storage as needed IfSetTypedValuewas instead called on theXPathNavigatorthat was
returned byXPathDocument, aNotSupportedExceptionwould be thrown as theXPathDocument
is read-only
TheBooks.xmldocument loaded from disk useshttp://example.books.comas its default namespace
Because theBooks.xsdXML Schema is associated with theBooks.xmldocument, and it assigns the
default namespace to behttp://example.books.com, the XPath must know how to resolve that
names-pace Otherwise, you cannot determine if an XPath expression with the wordbookin it refers to a book
from this namespace or another book entirely AnXmlNamespaceManageris created, andbis 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 namedbookis 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 TheXmlNamespaceManager
in Listing 10-12 is passed into the call toSelectNodesin 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
Trang 3work 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 anXDocumentobject by adding a reference to theSystem.Xml.XPath
namespace via a using or Imports statement Adding this reference adds new extension methods to the
XDocumentlikeCreateNavigatorget to anXPathNavigatorand the very useful XPathSelectElements
XPathSelectElements is similar to theSelectNodesandSelectSingleNodemethods of theSystem.Xml
.XmlDocument These extension methods are part of the ‘‘bridge classes’’ that provide smooth integration betweenSystem.XmlandSystem.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", "http://example.books.com")
’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", "http://example.books.com");
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 newNameTableand 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 asElements()andDescendants()rather than XPath
Trang 4TheSystem.Datanamespace andSystem.Xmlnamespace 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 withinSystem.DatauseXmlWriterandXmlReaderin a number of places Now that you’re more
familiar withSystem.Xmlconcepts, be sure to take note of the method overloads provided by the classes
withinSystem.Data For example, theDataSet.WriteXmlmethod has four overloads, one of which
takes inXmlWriter Most of the methods withSystem.Dataare 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 theResponseobject’s
OutputStreamproperty usingDataSet.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 theDataSetNameproperty DataSets contain one or more named
DataTableobjects, and the names of theseDataTablesdefine the wrapper element — in this case,
Customer The name of theDataTableis passed into theloadmethod of the DataSet The correlation
Trang 5between 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 TheXmlDataDocumentis 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, theXmlDocumentclass does not In order to bridge this gap, an unusual hybrid object, theXmlDataDocument, is introduced This object maintains the full fidelity of all the XML structure and allows you to access XML via theXmlDocument
API without losing the flexibility of a relational API AnXmlDataDocumentcontains 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
Trang 6TheXmlDataDocumentis a constructor that takes a DataSet as a parameter Any changes made to the
XmlDataDocumentare reflected in the DataSet and vice versa
Now take the DataSet loaded in Listing 10-14 and manipulate the data with theXmlDataDocumentand
DOM APIs you’re familiar with Next, jump back into the world ofSystem.Dataand 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");
Trang 7node.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 HTTPContentTypeand the call
toDataSet.WriteXml After the DataSet is loaded from the database, it is passed to theXmlDataDocument
constructor At this point, theXmlDataDocumentand the DataSet refer to the same set of information
TheEnforceConstraintsproperty of the DataSet is set tofalseto allow changes to the DataSet When
EnforceConstraintsis later set totrue, if any constraint rules were broken, an exception is thrown
AnXPathexpression is passed to the DOM methodSelectSingleNode, selecting theContactTitle
node of a particular customer, and its text is changed toBoss Then by callingGetRowFromElementon
theXmlDataDocument, the context jumps from the world of theXmlDocumentback to the world of the
DataSet Column names are passed into theindexingproperty of the returnedDataRow, and the output
is shown in this line:
Ana Trujillo is the Boss
The data is loaded from the SQL server and then manipulated and edited withXmlDocument-style
methods; a string is then built using aDataRowfrom 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
TheXmlDataSourcecontrol enables you to connect to your XML data and to use this data with any of
the ASP.NET data-bound controls Just like theSqlDataSourceand theAccessDataSourcecontrols, the
XmlDataSourcecontrol 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 http://examples.books.com It is
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 exists is very important The regrettable
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 DataListcontrol can take aXmlNamespaceManager as its second
parameter and query XML returned from the XmlDataSource — as long as the
control does not include an XPath attribute with namespace qualification or you can
Trang 8just 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 aDataListcontrol or any DataBinding-aware control and connect to an<asp:
XmlDataSource>control The technique for binding a control directly to theBooks.xmlfile 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="http://www.w3.org/1999/xhtml" >
<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 theXmlDataSourcecontrol You should focus
on two attributes in this example The first is theDataFileattribute 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 theXmlDataSourcecontrol is theXPathattribute TheXmlDataSource
control uses theXPathattribute for the filtering of XML data In this case, theXmlDataSourcecontrol
is taking everything within the<book>set of elements The value//bookstore/bookmeans that the
XmlDataSourcecontrol navigates to the<bookstore>element and then to the<book>element within
the specified XML file and returns a list of all books
TheDataListcontrol then must specify itsDataSourceIDas theXmlDataSourcecontrol In the
<ItemTemplate>section of theDataListcontrol, 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
Trang 9to 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 theBooks.xmlfile shown earlier, theXmlDataSource
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 These blogs, 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 atweblogs.asp.net.)
Figure 10-5
Trang 10Now that you know the location of the XML from the blog, you can use this XML with theXmlDataSource
control and display some of the results in aDataListcontrol The code for this example is shown in
Listing 10-17
Listing 10-17: Displaying an XML RSS blog feed
<%@ Page Language="VB"%>
<html xmlns="http://www.w3.org/1999/xhtml" >
<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="http://www.hanselman.com/blog/feed"
XPath="rss/channel/item">
</asp:XmlDataSource>
</form>
</body>
</html>
Looking at the code in Listing 10-17, you can see that theDataFilepoints to a URL where the XML
is retrieved TheXPathproperty filters and returns all the<item>elements from the RSS feed The
DataListcontrol 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 theDataFileproperty value in the following manner:
DataFile="http://www.someserver.com/GetWeather.asmx/ZipWeather?zipcode=63301"