Each class has unique pros and cons, as outlined earlier in this LISTING 11.6 Continued Using ADO.NET to Search, Filter, and Sort XML Data You can also search, filter, and sort XML data
Trang 1When the XML within the string is loaded into the StringReaderinstance (which is in turnpassed to the XmlTextReaderconstructor), it can be parsed like any other XML data Because theXmlTextReaderinstance is closed, theStringReaderwill automatically be closed as well.
Accessing XML Resources by Using the
The System.Xmlnamespace contains an abstract class named XmlResolverthat is responsible forresolving external resources, including items such as document type definitions (DTDs) andschemas Although a concrete instance of the XmlResolverclass can’t be created, XmlResolverhastwo child classes that derive from it—XmlUrlResolverand XmlSecureResolver—that can be instan-tiated and used These classes are used under the covers by different NET Framework classes,such as XmlDocument, XmlDataDocument, and XmlTextReader, and they can be accessed through theirrespective XmlResolverproperties
XmlUrlResolveris typically used when an external resource such as a DTD needs to be ignored,when security credentials need to be passed to a remote resource, or with XSLT stylesheets.Ignoring external resources is accomplished by setting the XmlResolverproperty of XML classessuch as XmlTextReaderand XmlDocumentto Nothing(nullin C#) This can be useful when the XMLdata needs to be parsed but a referenced DTD or schema doesn’t need to be resolved
An example of setting the XmlResolverproperty to Nothingis shown in Listing 11.2, where theMoreOver.com news feed is parsed to extract news headlines Because the DTD referenced in thedocument isn’t of use to the application, the XmlTextReaderclass’s XmlResolverproperty is set toNothing If the XmlResolverproperty were left in its default state, the DTD uniform resource iden-tifier (URI) would be resolved by an underlying XmlResolverproperty, assuming that access to theInternet is available However, if a proxy server blocked outside access to the DTD or if thenetwork were temporarily unavailable, the following error would occur:
The underlying connection was closed:
The remote name could not be resolved
You can also use the XmlUrlResolverclass to pass security credentials to a remote resource thatrequires authentication by using its Credentialsproperty Credentialsrepresents a write-onlyproperty of type ICredentials Listing 11.5 shows how you can create an XmlUrlResolverinstanceand assign it authentication credentials by using the NetworkCredentialclass (found in theSystem.Netnamespace) After you define the necessary credentials, you assign the XmlUrlResolverinstance to the XmlTextReaderclass’s XmlResolverproperty so that the secured XML document can
be parsed
LISTING 11.5 Passing Security Credentials to a Remotely Secured XML DocumentDim reader As XmlTextReader = Nothing
Dim xmlUri As String = “http://localhost/XMLChapterVB/Listing1.xml”
‘Get login credentialsDim uid As String = ConfigurationSettings.AppSettings(“UID”)
Trang 2Dim pwd As String = ConfigurationSettings.AppSettings(“Password”)Dim domain As String = ConfigurationSettings.AppSettings(“Domain”)Dim resolver As New XmlUrlResolver
resolver.Credentials = New NetworkCredential(uid, pwd, domain)Try
reader = New XmlTextReader(xmlUri)
‘Hook resolver to XmlTextReaderreader.XmlResolver = resolverWhile reader.Read() ‘Try to parse documentEnd While
Me.lblOutput.Text = “Parsed secured document.”
Catch exp As ExceptionMe.lblOutput.Text = “Did NOT parse secured document: “ + exp.MessageFinally
If Not (reader Is Nothing) Thenreader.Close()
End IfEnd Try
Version 1.1 of the NET Framework enhances security in the XslTransformclass by markingseveral overloaded versions of the XslTransformclass’s Load()and Transform()methods as obso-lete while adding new, more secure overloaded methods The XslTransformclass’s XmlResolverproperty has also been made obsolete in version 1.1 These changes prohibit XSLT scripts andextension objects, xsl:importand xsl:includestatements, and XSLT document()functions frombeing processed without supplying security evidence and/or an XmlResolverinstance whencalling the Load()and Transform()methods The following sections analyze changes to theXslTransformclass and explain the roles of the XmlResolverandEvidenceclasses
The Load()Method
The XslTransformclass’s Load()method found
in version 1.1 of the NET Framework hasseveral overloads that allow fine-grainedcontrol over whether XSLT scripts, extensionobjects, and xsl:import/xsl:includestate-ments are ignored during an XSLT transfor-mation When local XSLT stylesheets are used
in a transformation, the Load()method stillhas an overload that accepts a String-typeparameter containing the path to thestylesheet Using this overload is the easiestway to transform XML documents via XSLT because it automatically handles included orimported stylesheets as well as compiling embedded script within the stylesheet
LISTING 11.5 Continued
Loading Local XSLT Stylesheets
Using the overloaded Load()method andpassing the physical location of the XSLTstylesheet is fairly straightforward:
Dim xslPath as String = _Server.MapPath(“XSLT/Output.xslt”)Dim trans as New XslTransformtrans.Load(xslPath)
‘Perform transformation
‘with Transform() method
Trang 3In addition to the overload mentioned previously, there are several new overloads for the Load()method in version 1.1 of the NET Framework The following is one example:
Overloads Public Sub Load( _ByVal stylesheet As IXPathNavigable, _ByVal resolver As XmlResolver, _ByVal evidence As Evidence _)
The IXPathNavigableparameter represents the XSLT stylesheet used in the transformation Thisparameter accepts any object that implements the IXPathNavigableinterface, such as XmlDocument,XPathNavigator, or XPathDocument
The XmlResolverparameter is used to resolve XSLT documents imported into or included in amaster stylesheet When the parameter value is Nothing, imported or included documents areignored When a new XmlUrlResolverinstance is passed into the Load()method, documentsreferenced by xsl:importor xsl:includestatements are resolved and used in the transformation.The XmlUrlResolverclass’s Credentialsproperty can be used in cases where included or importedstylesheets require authentication in order to be accessed (Refer to Listing 11.5 for an example
of using the Credentialsproperty.)The evidenceparameter determines whether XSLT script blocks and extension objects areprocessed based on whether they are from trusted sources The parameter is based on theEvidencetype, located in the System.Security.Policynamespace The NET Framework SDKprovides the following insight into how Evidenceis used:
Security policy is composed of code groups; a particular assembly (the basic unit ofcode for granting security permissions) is a member of a code group if it satisfies thecode group’s membership condition Evidence is the set of inputs to policy thatmembership conditions use to determine to which code groups an assembly belongs.That is, by supplying a proper Evidenceobject to the Load()method, script code containedwithin potentially untrusted XSLT stylesheets can be compiled and used in an XML documenttransformation because the assembly that is generated can be assigned to the proper NETFramework code group If no Evidencetype is supplied, assemblies created during the compila-tion of XSLT script code cannot be used successfully due to their inherit security risks Forexample, when Nothingis passed for the Evidenceparameter, XSLT scripting, extension objects,and document()functions are ignored
In cases where a local XSLT stylesheet has embedded script, uses extension objects, or references
the document()function, the following code can be used to create the proper Evidenceobject forthe assembly:
Me.GetType().Assembly.Evidence
When a remote XSLT stylesheet containing script or extension object references is used in atransformation, the caller of the Load()method must supply evidence in order for script or
Trang 4extension objects to be executed properly To supply evidence, the XmlSecureResolverclass’sCreateEvidenceForUrl()method can be used The CreateEvidenceForUrl()method accepts a singleString-type parameter that contains the URL for which to create evidence, as shown here:
Dim uri as String = “some uri”
Dim xslDoc as new XPathDocument(uri)
‘Create EvidenceDim e as Evidence = _XmlSecureResolver.CreateEvidenceForUrl(uri)Dim trans as new XslTransform
trans.Load(xslDoc,new XmlUrlResolver(),e)
‘XSLT script, extension objects, etc can be used
‘since evidence was supplied
The Transform()Method
In addition to new overloaded Load()methods, the Transform()method has several new loads that expect an instance of an XmlResolverto be passed as a parameter The following is anexample of one of these overloads:
over-public void Transform(XPathNavigator, XsltArgumentList, _TextWriter, XmlResolver);
In cases where simple XSLT transformations (that is, transformations that involve a single XMLdocument and a single XSLT stylesheet stored locally) are performed, Nothing(nullin C#) can bepassed for the XmlResolverparameter:
Dim writer as new StringWriterDim xsl As New XslTransformxsl.Load(xslPath)
xsl.Transform(doc, Nothing, writer, Nothing)
Passing Nothingfor the XmlResolverparameter when more than one XML document is involved
in the transformation presents a problem For example, when the document()function is usedwithin the XSLT stylesheet to transform multiple XML documents simultaneously, passingNothingcauses any additional XML documents to be ignored In order to perform this type oftransformation successfully, a new XmlUrlResolverinstance must be passed to the Transform()method Listing 11.6 shows how this is done and highlights how evidence can be passed to theLoad() method in cases where a local XSLT stylesheet is used
LISTING 11.6 Using the XslTransformClass’s Load()and Transform()MethodsDim sw As New StringWriter
‘Load XML Doc and master XSLT StylesheetDim xmlDoc As New XPathDocument(Server.MapPath(“XML/Form.xml”))Dim xslDoc As New XPathDocument(Server.MapPath(“XSLT/Form.xslt”))
Trang 5‘Create XslTransform and load stylesheetDim trans1 As New XslTransform
Dim resolver As New XmlUrlResolvertrans1.Load(xslDoc, resolver, Me.GetType().Assembly.Evidence)
‘Transform XMLtrans1.Transform(xmlDoc, Nothing, sw, resolver)Response.Write(sw.ToString())
sw.Close()
Searching, Filtering, and Sorting XML Data
A little over a year after the XML 1.0 specification was released by the World Wide WebConsortium (W3C), the XPath language emerged on the scene to fill a void created by theinability to effectively search and filter XML data Since its release, XPath has become increas-ingly important in the world of XML and is used in DOM APIs, XSLT stylesheets, XSD schemas,and other XML-specific languages
XPath is a path-based language (it resembles DOS paths in some regards) that allows specializedstatements capable of searching and filtering nodes to be executed against XML documents Thischapter does not provide a complete explanation of the XPath language; for more details on the
XPath language, see the book XML for ASP.NET Developers from Sams Publishing The following
is a sample XPath statement that uses axes, node tests, and a predicate:
/customers/customer[@id=’ALFKI’]
This XPath statement uses the ChildandAttributeaxes, along with node tests and apredicate (the text within the square brackets)
to search for an element named customerthathas an idattribute value equal to ALFKI.When the statement is executed, unwantednodes are automatically filtered out, and thedesired node is returned (assuming that it isfound) Although quite simple, this XPathstatement demonstrates the power of search-ing and filtering data located in an XMLdocument The following sections show how different NET Framework classes can be usedalong with XPath to search, filter, and sort data
Searching and Filtering XML Data
The NET Framework contains several classes that are capable of searching and filtering XMLdata using the XPath language Each class has unique pros and cons, as outlined earlier in this
LISTING 11.6 Continued
Using ADO.NET to Search, Filter, and Sort XML Data
You can also search, filter, and sort XML data
by using the DataSetclass and its relatedclasses After loading XML data into aDataSetinstance by using the ReadXml()method, you can use properties and methods
of the DataTableand DataViewclasses toaccomplish tasks similar to those that theXPath language handles
Trang 6chapter, and offers different levels of efficiency The XPathNavigatorclass is designed to workhand-in-hand with the XPath language to provide a read-only cursor-style model for navigatingXML nodes Other classes, such as XmlDocumentand XmlNode, provide XPath support through theirSelectNodes()and SelectSingleNode()methods.
When designing applications that consume XML data, you should first look to theXPathNavigatorclass (located in the System.Xml.XPathnamespace) when you need to search XMLdata Although XPathNavigatorisn’t as fast as the forward-only API provided by the XmlTextReaderclass and doesn’t provide the editing capabilities found in the DOM API (this will change inversion 2.0 of the NET Framework when classes such as XPathEditorare introduced), it can beuseful in applications that need the ability to traverse an XML document’s hierarchy along avariety of axes The XPathNavigatorclass offers numerous benefits, such as compiled XPath state-ments and the ability to leverage the IXPathNavigableinterface to search non-XML data stores
The XPathNavigatorclass is abstract, so it can’t be created directly However, you can use classesthat implement the IXPathNavigableinterface (XmlDocument, XmlDataDocument, XmlNode, andXPathDocument) to create a concrete instance of the XPathNavigatorclass by using
CreateNavigator() After the XPathNavigatorinstance is created, you can navigate through theXML document one node at a time When you are positioned on a node, you can reach othernodes located before or after the current node by calling a variety of methods, such asMoveToNext(), MoveToParent(), and MoveToFirstChild()
You can also use XPathNavigatorto search and filter nodes within an XML document by usingXPath statements By leveraging XPath, you can greatly reduce the amount of code that needs
to be written to gather data, thus making applications easier to maintain Nodes returned fromexecuting an XPath statement are placed in an XPathNodeIteratorinstance that can be iteratedthrough easily Before looking at an example of using XPathNavigator’s methods, you shouldexamine the XML document in Listing 11.7, which contains book and author data
LISTING 11.7 An XML Document That Contains Book and Author Data
<?xml version=”1.0”?>
<bookstore>
<book genre=”novel” style=”hardcover”>
<title>The Handmaid’s Tale</title>
<book genre=”novel” style=”hardcover”>
<title>The Worker’s Tale</title>
<author>
<first-name>Margaret</first-name>
<last-name>Atwood</last-name>
</author>
Trang 7LISTING 11.8 Navigating XML Data by Using XPathNavigatorDim sb as New StringBuilder
Private Sub NavigateBooks()Dim xmlPath As String = Server.MapPath(“Listing7.xml”)
‘Load XML into a non-editable structure
‘This is more efficient than the DOMDim doc As New XPathDocument(xmlPath)
‘Create XPathNavigator by calling CreateNavigator() methodDim nav As XPathNavigator = doc.CreateNavigator()
‘Move to documentnav.MoveToRoot()
‘Move to root element - bookstorenav.MoveToFirstChild()
‘Move to first book child element
Private Sub WalkSiblings(ByVal nav As XPathNavigator)
‘Move to “title” element and get valueDim firstName As String = String.Empty
LISTING 11.7 Continued
Trang 8Dim lastName As String = String.Emptynav.MoveToFirstChild()
Dim title As String = nav.Valuesb.Append((title + “<br />”))
‘Move back to book elementnav.MoveToParent()
‘access author element under bookDim authorNode As XPathNodeIterator = _nav.SelectChildren(“author”, “”)While authorNode.MoveNext()
‘Move to first-name elementauthorNode.Current.MoveToFirstChild()firstName = authorNode.Current.Value
‘Move to last-name elementauthorNode.Current.MoveToNext()lastName = authorNode.Current.Valuesb.Append((firstName + “ “ + lastName + “<br />”))End While
‘Now move to price elementDim priceNode As XPathNodeIterator = _nav.SelectChildren(“price”, “”)priceNode.MoveNext()
‘Write out value of price elementsb.Append((“$” + priceNode.Current.Value + “<br />”))
‘Search books by author and filter out unwanted booksDim otherBookNodes As XPathNodeIterator = _
nav.Select((“//book[author/first-name=’” + firstName + _
“‘ and author/last-name=’” + lastName + _
“‘ and title != “”” + title + “””]/title”))sb.Append(“<i>Other Books:</i> <br />”)
‘Add other books to output
If otherBookNodes.Count > 0 ThenWhile otherBookNodes.MoveNext()sb.Append((otherBookNodes.Current.Value + “<br />”))End While
Elsesb.Append(“None”)End If
sb.Append(“<p />”)End Sub
LISTING 11.8 Continued
Trang 9Sorting XML Data
In the past, applications that required XMLdata to be sorted have typically relied onXSLT and the xsl:sortelement due to XPath’s lack of native support for sortingdata Although using XSLT to sort can get the job done, writing stylesheets andtemplates is often overkill and doesn’t workfor all types of data sorts In fact, the XSLT 1.0 specification only supports text and numericsorts “out of the box.”
Fortunately, the reliance on XSLT to sort XML data is minimized in the NET Framework due tonative XML sorting capabilities found in the XPathExpressionand DataViewclasses In addition tobeing able to perform textual and numeric sorts, you can use the XPathExpressionclass toperform custom sorts, using objects that implement the IComparerinterface You can also use theDataViewclass to sort data loaded into a DataTableinstance The following sections demonstratehow to sort XML data by using the XPathNavigatorand XPathExpressionclasses and providedetails on how to leverage the IComparerinterface They also demonstrate how to sort XML data
by using XSD schemas, along with DataTableand DataViewinstances
Sorting with the XPathExpressionClass
You can sort XML nodes by first compiling an XPath statement into an XPathExpressionobject.This is accomplished by calling the XPathNavigatorclass’s Compile()method Then you can add atext or numeric sort to the XPathExpressionobject by calling its AddSort()method AddSort()hastwo overloads:
FIGURE 11.1 Accessing bookand author
nodes by using theXPathNavigatorAPI
Alternatives to XPathNavigator
The XmlDocumentand DataSetclasses couldalso be used to search and filter the XMLdocument shown in Listing 11.7 However,because no editing operations were performed,the XPathDocumentand XPathNavigatorcombination provides a more efficient solution
Trang 10Overloads Public MustOverride Sub AddSort( _ByVal expr As Object, _
ByVal comparer As IComparer _)
Overloads Public MustOverride Sub AddSort( _ByVal expr As Object, _
ByVal order As XmlSortOrder, _ByVal caseOrder As XmlCaseOrder, _ByVal lang As String, _
ByVal dataType As XmlDataType _)
The first of these overloads allows a custom object implementing IComparerto be used toperform sorts This is useful when more advanced sorts need to take place The second overloadaccepts a sort key, the sort order (ascending or descending), a value indicating how to sortuppercase and lowercase text, a language value, and the type of sort to perform (text ornumeric) Listing 11.9 shows how to use the Compile()and AddSort()methods to sort the newsheadlines shown in Listing 11.1 The code sorts the headlines based on the titleelement, inascending order
LISTING 11.9 Sorting XML Data by Using the XPathNavigatorand XPathExpressionClassesDim sorted As New StringBuilder
‘XPath statementDim xpath As String = “/moreovernews/article/headline_text”
‘Create XPathDocument class so we can get a navigatorDim doc As New XPathDocument(Server.MapPath(“Listing1.xml”))Dim nav As XPathNavigator = doc.CreateNavigator()
‘Compile xpath expressionDim exp As XPathExpression = nav.Compile(xpath)
‘Add a sort based upon the headline_text child text nodeexp.AddSort(“text()”, XmlSortOrder.Ascending, XmlCaseOrder.None, _
“”, XmlDataType.Text)
‘select nodes so we can see the sortDim it As XPathNodeIterator = nav.Select(exp)While it.MoveNext()
‘Grab headline_text valueDim headline As String = it.Current.Value
‘Move to articleit.Current.MoveToParent()
‘Move to urlit.Current.MoveToFirstChild()
Trang 11‘Grab urlDim url As String = it.Current.Valuesorted.Append(“<tr><td><a href=”””)sorted.Append(url)
sorted.Append(“””>”)sorted.Append(headline)sorted.Append(“</a></td></tr>”)End While
Me.lblNews.Text = sorted.ToString()
Although this type of sorting works well for basic text or numeric sorts, what if you need to sort
a set of nodes based on a Datedata type? Fortunately, one of the AddSort()overloads shownearlier in this section allows a custom object that implements the IComparerinterface to bepassed to it IComparerhas a single method, named Compare(), that you can use to perform avariety of object comparisons Listing 11.10 shows a simple class named DateComparerthatimplements the Compare()method
LISTING 11.10 Creating a Custom Sort Class That Implements IComparerImports System.Collections
Public Class DateComparer : Implements IComparer
Public Function Compare(ByVal date1 As Object, _ByVal date2 As Object) As Integer Implements IComparer.CompareDim intResult As Integer
Dim d1 As DateTime = Convert.ToDateTime(date1)Dim d2 As DateTime = Convert.ToDateTime(date2)intResult = DateTime.Compare(d1, d2)
Return intResult * -1End Function
End Class
The DateComparerclass works by accepting two objects that are converted to DateTimetypes.Upon conversion, the objects are compared to each other, using the DateTimeobject’s Compare()method Compare()returns an integer value from -1to 1, depending on how the dates compare
A value of 0means that the two dates are equal, and a value of -1or 1means that one of thedates is greater than the other (See the NET Framework SDK for more details.) The integercreated by calling DateTime.Compare()is returned from the DateComparerclass’s Compare()methodand used by the XPathExpressionclass to perform the sorting Listing 11.11 shows an example ofusing DateComparerin conjunction with the XPathExpressionclass
LISTING 11.9 Continued
Trang 12LISTING 11.11 Performing Custom Sorts with the XPathExpressionClassDim sorted As New StringBuilder
Dim xpath As String = “/moreovernews/article/harvest_time”
‘Create XPathDocument class so we can get a navigatorDim doc As New XPathDocument(Server.MapPath(“Listing1.xml”))Dim nav As XPathNavigator = doc.CreateNavigator()
‘Compile xpath expression so we can add a sort to itDim exp As XPathExpression = nav.Compile(xpath)
‘Create IComparer objectDim dc As New DateComparer
‘Pass IComparer object to AddSort()exp.AddSort(“text()”, dc)
‘select nodes so we can see the sortDim it As XPathNodeIterator = nav.Select(exp)While it.MoveNext()
‘Grab harvest_time valueDim [date] As String = it.Current.Value
‘Move to article parentit.Current.MoveToParent()
‘Move to urlit.Current.MoveToFirstChild()
‘Grab urlDim url As String = it.Current.Value
‘Move to headlineit.Current.MoveToParent()Dim headlineIt As XPathNodeIterator = _it.Current.SelectChildren(“headline_text”, String.Empty)headlineIt.MoveNext()
Dim headline As String = headlineIt.Current.Value
sorted.Append(“<tr><td><a href=”””)sorted.Append(url)
sorted.Append(“””>”)sorted.Append(headline)sorted.Append(“</a></td><td>”)sorted.Append([date])
sorted.Append(“</td></tr>”)End While
‘Add data to a PlaceHolder server control named phNews Me.phNews.Controls.Add(New LiteralControl(sorted.ToString()))
Trang 13Figure 11.2 shows the HTML output generated after running the code shown in Listing 11.11.Notice that the news headlines are properly sorted by date and time.
FIGURE 11.2The result of sorting XML nodesbased on date and time
Sorting with the DataViewClass
You can use ADO.NET’s DataViewclass in combination with the DataSetand DataTableclasses tosort XML data Using the DataViewclass to sort XML is generally attractive to ASP.NET developersbecause they can use it to bind views to a variety of ASP.NET controls, including the DataGridcontrol When you set it up properly, you can even use the DataViewclass to sort dates foundwithin XML data, without resorting to using a custom class that implements the IComparerinter-face discussed earlier
To sort by using the DataViewclass, you must first load the XML data into a DataSetinstance bycalling the ReadXml()method You can then create a DataViewobject based on the appropriateDataTablewithin the DataSetinstance To sort based on a specific column, you assign theDataColumnname to the DataViewobject’s Sortproperty You can then assign the DataViewobject
to the DataSourceproperty of a variety of ASP.NET Web controls
Following these steps works well for text-based sorts, but what happens if you want to sortnumerically or by date? To sort based on dates contained within an XML document, the columnrepresenting the date data must be defined as a DateTimetype within the DataTableobject.Although you can do this programmatically, a more flexible solution is to preload an XSDschema that describes the XML document structure and its types into the DataSetobject TheXSD schema can be loaded by calling the DataSetobject’sReadXmlSchema()method When youload the schema into the DataSetinstance, all the DataColumninstances will be properly typed sothat sorting can occur on different types (such as DateTime) by using the DataViewobject
Before showing the code to sort XML data by date using a DataViewinstance, we need tomention a gotcha XSD schema date types format dates differently than do Common Language
Trang 14Runtime (CLR) DateTimetypes For example, you can load the harvest_timeelement shown inListing 11.1 into a DateTimestructure by using its Parse()method:
Dim date as DateTime = DateTime.Parse(“Jan 14 2004 8:57PM”)
However, this date is not valid, according to the XSD schema specification As a result, it willcause an error when it is loaded into a DataSetinstance that has been preloaded with an XSDschema defining the harvest_timeelement as a Datedata type To make the data conform to theDatedata type defined in the schema specification, you need to change it to the followingformat:
2004-01-14T20:57:00.0000000-07:00
Although you could potentially do this conversion by hand, the XmlConvertclass can handle itwith a single line of code (see Listing 11.12) Failure to properly perform this conversion willresult in an error when the XML is loaded into the DataSetinstance:
String was not recognized as a valid DateTime
Although this gotcha causes a minor inconvenience when you’re trying to sort the news data inListing 11.1 by harvest_time, you can easily overcome it by using the XmlDocumentand XmlConvertclasses to manipulate the date values The code in Listing 11.12 shows how to use these classes
as well as perform several other tasks, including the following:
n Loading the news XML data into the DOM
n Converting all harvest_timetext node values to valid schema Datedata types by using theXmlConvertclass’s ToString()method
n Serializing the DOM structure to a MemoryStreamobject
n Loading the MemoryStreamobject into a DataSetinstance that is preloaded with an XSDschema to properly type the different DataTablecolumns
n Creating a DataViewobject based on the DataSetobject’s first DataTable
n Identifying a sort column by using the DataViewobject’s Sortproperty
n Binding the DataViewto an ASP.NET DataGridserver control
LISTING 11.12 Sorting XML Data by Using the DataViewClass
‘Fix Listing1.xml dates to be schema “compatible” using DOMDim doc As New XmlDocument
doc.Load(Server.MapPath(“Listing1.xml”))
‘Find all harvest_time nodesDim dateNodes As XmlNodeList = doc.SelectNodes(“//harvest_time”)For Each dateNode as XmlNode In dateNodes
Dim newDate As DateTime = DateTime.Parse(dateNode.InnerText)
‘Convert harvest_time string to XSD Schema data type string
Trang 15dateNode.InnerText = XmlConvert.ToString(newDate)Next dateNode
‘Save updated harvest_time XML to a StreamDim ms As New MemoryStream
doc.Save(ms)ms.Position = 0
Dim ds As New DataSet
‘Load schemads.ReadXmlSchema(Server.MapPath(“Listing12.xsd”))
‘Load XML data into DataSetds.ReadXml(ms)
‘Create DataViewDim view As DataView = ds.Tables(0).DefaultView
‘Sort on date columnview.Sort = “harvest_time DESC”
Me.dgNews.DataSource = viewMe.dgNews.DataBind()ms.Close()
Figure 11.3 shows the result of sorting the XML headlines based on harvest_time.LISTING 11.12 Continued
FIGURE 11.3The result of sorting XML nodesbased on date and time with aDataViewinstance
Searching Namespace Qualified Nodes
The NET Framework prevents naming collisions by logically organizing classes into namespaces.XML documents also prevent naming collisions by using namespaces, although the way they
Trang 16are defined is quite different Namespace qualified nodes are logically separated from othernodes (think of XML nodes as being organized into different rooms in a building based on theirnamespace URIs) to make them easy to locate and to avoid collisions Two different types ofXML namespaces exist: default and local
The following is an example of defining a default namespace:
Public Overridable Sub AddNamespace( _ByVal prefix As String, _
ByVal uri As String _)
Although XmlNamespaceManageris often used when namespaces need to be dynamically addedinto XML fragments, it can also be used when executing XPath statements To query articlenodes located in a default namespace (such as the one shown earlier in this section), you can add the default namespace to the XmlNamespaceManagerinstance and then use it in the XPath statement The code shown in Listing 11.13 illustrates this process Adding theXmlNamespaceManagernamespace data into the context of the XPath statement is accomplished
by using the XPathExpressionclass’s SetContext()method
Trang 17LISTING 11.13 Searching for Nodes in a Default Namespace by Using XpathNavigatorDim xmlPath As String = Server.MapPath(“Listing13.xml”)
‘Load XMLDim doc As New XPathDocument(xmlPath)
‘Create navigatorDim nav As XPathNavigator = doc.CreateNavigator()Dim ns As New XmlNamespaceManager(nav.NameTable)
‘Define default namespace Prefix can be any valid XML namespace
‘prefix valuens.AddNamespace(“ns”, “http://www.moreover.com”)
‘Add default prefix into xpath statement to account for
‘default namespaceDim xpath As String = “/ns:moreovernews/ns:article/” + “ns:headline_text”
‘Create a compiled xpath statement and set context to include
‘the namespace manager data
Dim exp As XPathExpression = nav.Compile(xpath)exp.SetContext(ns)
‘Select nodes and write out the headlinesDim it As XPathNodeIterator = nav.Select(exp)While it.MoveNext()
‘Create navigatorDim nav As XPathNavigator = doc.CreateNavigator()Dim ns As New XmlNamespaceManager(nav.NameTable)
‘Define news namespace prefix and URK
ns.AddNamespace(“news”, “http://www.moreover.com”)
‘Add news prefix into xpath statementDim xpathNS As String = “/moreovernews/news:article/headline_text”
‘Create a compiled xpath statement and set context to include
‘the namespace manager data
Trang 18Dim exp As XPathExpression = nav.Compile(xpathNS)exp.SetContext(ns)
‘Select nodes and write out the headlinesDim it As XPathNodeIterator = nav.Select(exp)While it.MoveNext()
Me.lblNoNamespace.Text += it2.Current.Value + “<br />”
End While
You can also use the XmlNamespaceManagerobject to search for namespace qualified nodes, usingthe XmlDocumentclass, as shown in Listing 11.15 The XmlDocumentclass’s SelectNodes()method(which is inherited from XmlNode) contains an overload that accepts an XmlNamespaceManagerobject as a parameter
LISTING 11.15 Searching for Nodes in a Local Namespace by Using XmlDocumentDim xmlPath As String = Server.MapPath(“Listing14.xml”)
Dim doc As New XmlDocumentdoc.Load(xmlPath)
Dim ns As New XmlNamespaceManager(doc.NameTable)ns.AddNamespace(“news”, “http://www.moreover.com”)Dim xpathLocal As String = “/moreovernews/news:article/headline_text”
Dim newsNodes As XmlNodeList = doc.SelectNodes(xpathLocal, ns)For Each newsNode As XmlNode In newsNodes
Trang 19Creating a Reusable XML Validation Class
In addition to creating Web form front-end code, ASP.NET programmers are often charged withdeveloping a variety of back-end processes, such as those that access remote XML data and store
it in a database for later retrieval These types of processes may involve validating the XML data
to ensure that it is structured properly and contains valid data types that properly match upwith database fields By validating XML data first, you can catch potential errors ahead of time,before any SQL statements are executed in the database
The NET Framework supports validating XML documents using several different types of ments including DTDs, XML Data-Reduced (XDR) schemas, and XSD schemas XSD schemasoffer the most power and flexibility of the three choices, through their support for validating adocument’s structure as well as the data types it contains You can find more information aboutXSD schemas at the W3C Web site: www.w3.org
docu-XML documents can be programmatically validated by using the XmlValidatingReaderclasslocated in the NET Framework’s System.Xmlnamespace You can use this class to validate docu-ments against DTD, XDR, or XSD schema documents Like the XmlTextReaderclass, it provides afast, forward-only API that can handle large XML documents quickly and efficiently
XmlValidatingReaderexposes a ValidationHandlerevent that is called when the validation processerrors, such as when incorrect element nesting or invalid data types are encountered
Although you can write validation code from scratch each time you need to validate an XMLdocument, encapsulating validation code into a wrapper class brings many object-orientedcoding benefits, including encapsulation and code reuse If you write a wrapper class, developerswith different skill levels can perform XML document validation more easily; also, validationcode can be simplified when multiple applications share the same code base
Listing 11.16 contains the skeleton for a reusable XML validation component that uses theXmlValidatingReaderclass The XmlValidatorclass relies on a helper structure named
XmlValidationStatusto report if XML documents are valid to calling applications
LISTING 11.16 A Skeleton for a Reusable XML Validator Class and Helper StructurePublic Class XmlValidator
Public Function Validate(ByVal xml As Object, _ByVal schemaCol As XmlSchemaCollection, _ ByVal dtdInfo() As String, ByVal logError As Boolean, _ ByVal logFile As String) As XmlValidationStatus End Function
Private Sub ValidationCallBack(ByVal sender As Object, _ ByVal args As ValidationEventArgs)
End Sub
Trang 20The logErrorand logFileparameters are self-explanatory, but the others need further tion The xmlparameter is typed as Objectto allow different types of XML data sources to bevalidated Valid XML data source types include StringReader, String, and Stream Passing anyother types for the xmlparameter value will cause an ApplicationExceptionerror to be thrown.
explana-The schemaColparameter accepts an XmlSchemaCollectioninstance (XmlSchemaCollectionis located
in the System.Xml.Schemanamespace) that contains one or more schemas used to validate theXML data source When DTDs are used for validation, the DTD DocTypeName(the root element ofthe XML document) is passed as the first item in the Stringarray, followed by the physical path
to the DTD document Listing 11.17 shows the complete code for the Validate()method
LISTING 11.17 The Validate()and ValidationCallBack()MethodsPrivate _valid As Boolean
Private _logError As BooleanPrivate _logFile As StringPrivate _validationErrors As String = String.EmptyPrivate xmlReader As XmlTextReader = NothingPrivate vReader As XmlValidatingReader = Nothing
Public Function Validate(ByVal xml As Object, _ByVal schemaCol As XmlSchemaCollection, _ByVal dtdInfo() As String, ByVal logError As Boolean, _ByVal logFile As String) As XmlValidationStatus
_logError = logError_logFile = logFile_valid = True
Try
‘Check what type of XML data source was passed
If TypeOf xml Is StringReader ThenxmlReader = New XmlTextReader(CType(xml, StringReader))ElseIf TypeOf xml Is String Then
xmlReader = New XmlTextReader(CType(xml, String))
LISTING 11.16 Continued
Trang 21ElseIf TypeOf xml Is Stream ThenxmlReader = New XmlTextReader(CType(xml, Stream))Else
Throw New ApplicationException(“Invalid XML data “ + _
“source passed.”)End If
‘Hookup DTD or Schemas
If Not (dtdInfo Is Nothing) Then
If dtdInfo.Length > 0 ThenDim context As New XmlParserContext(Nothing, Nothing, _dtdInfo(0), “”, dtdInfo(1), “”, dtdInfo(1), “”, _XmlSpace.Default)
xmlReader.MoveToContent()vReader = _
New XmlValidatingReader(xmlReader.ReadOuterXml(), _XmlNodeType.Element, context)
vReader.ValidationType = ValidationType.DTDEnd If
ElsevReader = New XmlValidatingReader(xmlReader)vReader.ValidationType = ValidationType.Auto
If Not (schemaCol Is Nothing) ThenvReader.Schemas.Add(schemaCol)End If
End If
‘Associate validating reader with callback method
‘to handle any validation errorsAddHandler vReader.ValidationEventHandler, _AddressOf Me.ValidationCallBack
‘ Parse through XML documentWhile vReader.Read()
End WhileCatch_valid = FalseFinally ‘Close validating reader
If Not (vReader Is Nothing) ThenvReader.Close()
End IfEnd Try
‘Report back to calling application
LISTING 11.17 Continued
Trang 22Dim status As New XmlValidationStatusstatus.Status = _valid
status.ErrorMessages = _validationErrorsReturn status
writer.WriteLine()writer.WriteLine((args.Message + “ “ + today.ToString()))writer.WriteLine()
If xmlReader.LineNumber > 0 Thenwriter.WriteLine((“Line: “ + xmlReader.LineNumber + _
“ Position: “ + xmlReader.LinePosition))End If
writer.WriteLine()writer.Flush()Else ‘Track error messages_validationErrors = args.Message + “ Line: “ + _xmlReader.LineNumber.ToString() + _
“ Column:” + xmlReader.LinePosition.ToString() + _ControlChars.Lf + ControlChars.Lf
End IfCatchFinally ‘Ensure StreamWriter gets closed
If Not (writer Is Nothing) Thenwriter.Close()
End IfEnd TryEnd Sub
Validate()starts by loading the XML data source into an XmlTextReaderinstance, hooking
up schemas or DTDs, and then instantiating the XmlValidatingReaderinstance Any errorsLISTING 11.17 Continued
Trang 23encountered during the XML validationprocess cause the ValidationCallBackmethod
to be called; this method handles trackingand logging errors Upon completion, the Validate()method creates anXmlValidationStatusstructure and assignsappropriate values to its fields
Listing 11.18 provides an example of puttingthe XmlValidatorclass to use Any errorsfound during the validation operation arewritten back to the page in this example, butthey could instead be logged to a file
LISTING 11.18 Using the XmlValidatorClass to Validate XML Data
‘Define logging folder to use when logging is turned onDim logFile As String = Server.MapPath(“Log.txt”)Dim xmlFilePath As String = Server.MapPath(“Listing18.xml”)
‘Create schema collection object and add schema to itDim schemaCol As New XmlSchemaCollection
If valStatus.Status = True ThenMe.lblOutput.Text = “<b>Validation was SUCCESSFUL!</b>”
‘Call method to process XML document for backend processElse
Me.lblOutput.Text = “<b>Validation failed!</b><p />”
Me.lblOutput.Text += valStatus.ErrorMessagesEnd If
Converting Relational Data to XML
Web applications have come a long way since the early days of the Internet Many of the firstapplications relied on data stored in local databases or flat files and provided little to no flexibil-ity for accessing data from distributed sources As the Internet has evolved, more advanced dataaccess technologies have come about that allow data from a variety of locations and sources to
be used in Web applications This has resulted in companies automating business processes andultimately cutting operational costs
Other Uses for the XmlValidator Class
Although the example in Listing 11.18 usesthe XmlValidatorclass from within anASP.NET page, a more realistic and usefulapproach might be to have a Windowsservice that automatically grabs XML docu-ments from a variety of locations and vali-dates them Valid XML documents could then
be moved into a database or stored on thefile system for later retrieval You can find anexample of creating a Windows service forthis purpose at www.xmlforasp.net/
codeSection.aspx?csID=77
Trang 24ADO.NET represents one of the most powerful technologies to have evolved out of the old nologies of the Internet When you use ADO.NET, only a few lines of code are required to loaddata from a relational database and convert it to XML for transport between different businessentities, binding to hierarchical controls, transformation with XSLT, and many other purposes.
tech-The following sections provide several pure NET Framework techniques for converting tional data to XML and show how you can customize the structure of an XML document
rela-Customizing XML by Using the DataSet Class
The DataSetclass exposes two methods named GetXml()and WriteXml()that can be used to easilyconvert relational data into XML GetXml()returns a string that contains the XML data, andWriteXml()can write XML data to a file or to a TextWriter, Stream, or XmlWriterinstance Bothmethods generate XML documents that are element-centric The root node of the generatedXML is named after the DataSetinstance, and each child of the root is named based onDataTableinstances in the DataSetinstance
Although the default XML structure generated by the DataSetinstance might be fine for someapplications, others might require the structure to be customized so that the data can be inte-grated into another application or matched up with a schema You can customize the XMLstructure by using the DataColumnand DataRelationclasses You can use theDataColumnclass tocontrol whether data is mapped to elements or attributes, and you can use the DataRelationclass
Attribute Data is mapped to an attribute
Hidden Data is not output in the generated XML
SimpleContent Data is mapped to an XmlTextnode
Changing the MappingTypevalue allows you to shape the XML data as desired Listing 11.19demonstrates how to load data from the Northwind database’s Customerstable into a DataSetinstance and use the ColumnMappingproperty of the DataColumnclass to associate primary key datawith an attribute
LISTING 11.19 Shaping XML Data by Using the ColumnMappingPropertyDim connStr As String = ConfigurationSettings.AppSettings(“ConnStr”)Dim sql As String = “SELECT * FROM Customers “ + _
“WHERE CustomerID = ‘ALFKI’”
Dim conn As New SqlConnection(connStr)
Trang 25Dim da As New SqlDataAdapter(sql, conn)
‘Provide root name for XML documentDim ds As New DataSet(“Customers”)
‘Provide name for each child element of rootda.Fill(ds, “Customer”)
‘Map CustomerID field to an attributeds.Tables(0).Columns(“CustomerID”).ColumnMapping = _MappingType.Attribute
Me.txtXml.Text = ds.GetXml()conn.Close()
The following XML is generated after running the code in Listing 11.19 (notice that the ment’s root node is named after the DataSetinstance and that the CustomerIDdata is defined as
LISTING 11.20 Nesting XML Based on Primary/Foreign-Key RelationshipsDim connStr As String = ConfigurationSettings.AppSettings(“ConnStr”)Dim sql As String = “SELECT * FROM “ + _
“Customers WHERE CustomerID = ‘ALFKI’;”
sql += “SELECT * FROM Orders WHERE CustomerID = ‘ALFKI’”
LISTING 11.19 Continued