XML
XML is found in many aspects of the .NET Framework, even when you are not using it directly. It is the serialization format used
for Web Services, and is found behind the scenes with the DataSet. Because of this pervasiveness of XML across the framework, the
exam will test your knowledge on working with XML directly as well.
Accessing XML using the DOM
There are three classes you can use to store and manipulate XML data:
| Class |
Description |
| XmlDocument |
Provides a DOM level interface for manipulating XML documents |
| XmlDataDocument |
An extension of XmlDocument that can expose the XML as a DataSet |
| XPathDocument |
A small storage object designed for access via an XPathNavigator and XPath
queries. |
The following code sample loads an XML document, and selects all
<registrant> nodes by using the GetElementsByTagName function of the
XmlDocument object. This returns an XmlNodeList, which we then iterate through
and display each node's values.
using
System;
using
System.Xml;
namespace
LearningXml
{
 class
DomExample
 {
   static
void
Main(string[]
args)
   {
           XmlDocument
objXmlDoc
=
new
XmlDocument();
     objXmlDoc.Load("books.xml");
           XmlNodeList
objNodes
=
objXmlDoc.GetElementsByTagName("registrant");
           foreach(XmlNode
objNode
in
objNodes)
     {
       Console.WriteLine("Found Element: "
+
objNode.LocalName);
       Console.WriteLine(" First Name: "
+
objNode.Attributes["FirstName"].Value);
       Console.WriteLine("  Last Name: "
+
objNode.Attributes["LastName"].Value);
     }
   }
 }
}
Running the console application results in the following:
XML and the DataSet
At the heart of the .NET class library is XML, and this is true with the
DataSet as well. Here are some of the XML features of the DataSet:
WriteXml
The WriteXml method writes the data, and optionally the schema, in a DataSet.
There are several overloads of this method, allowing you to write the XML to
any Stream, file, TextWrite, or XmlWriter, as well as overloads allowing you to
write the schema as well. Here are the different overloads of the WriteXml
method:
public
void
WriteXml(Stream);
public
void
WriteXml(Stream,
XmlWriteMode);
public
void
WriteXml(string);
public
void
WriteXml(string,
XmlWriteMode);
public
void
WriteXml(TextWriter);
public
void
WriteXml(TextWriter,
XmlWriteMode);
public
void
WriteXml(XmlWriter);
public
void
WriteXml(XmlWriter,
XmlWriteMode);
The values for the XmlWriteMode enumeration are:
| Value |
Description |
| DiffGram |
Writes the DataSet as a DiffGram, including changed and original values. |
| IgnoreSchema |
Writes the XML without any schema |
| WriteSchema |
Default. Writes the XML data with an inline schema. |
When you write the XML, by default it will have the following format:
<?xml version="1.0" standalone="yes"?>
<NewDataSet>
<Table>
<customerID>8</customerID><
br> <customerName>Wily
Widgets</customerName>
</Table>
</NewDataSet>
The root node of the XML document is the name of the DataSet. This can be
indicated at time of instantiation, or later with the DataSetName property, as
in the following lines of code:
DataSet
objDataSet
=
new
DataSet("CustomersDataSet");
objDataSet.DataSetName
=
"CustomersDataSet";
The rows of data are enclosed in a tag with the name of the table, set when you
use a DataAdapter to fill the DataSet, or by setting the TableName property of
the Table in the DataSet. The following code snippet shows these two cases:
SqlDataAdapter
da
=
new
SqlDataAdapter(objCmd);
da.Fill(objDataSet,
"CustomersTable");
objDataSet.Tables[0].TableName
=
"CustomersTable";
Using the WriteXml method after setting these properties results in the XML
file below. Notice our DataSet name, CustomersDataSet, and our Table name,
CustomersTable, in the output:
<?xml version="1.0" standalone="yes"?>
<CustomersDataSet>
<CustomersTable>
<customerID>8</customerID>
<customerName>Wily
Widgets</customerName>
</CustomersTable>
</CustomersDataSet>
If you would prefer to have the columns mapped as attributes of the XML
element, rather than individual elements, you have to change the ColumnMapping
property of each column in your DataSet Table to MappingType.Attribute, as in
the following example:
foreach(DataColumn c
in
objDataSet.Tables[0].Columns)
{
c.ColumnMapping
=
MappingType.Attribute;
}
Here is the output using the same Customers data as in the previous examples:
<?xml version="1.0" standalone="yes"?>
<CustomersDataSet>
<CustomersTable customerID="8" customerName="Wily Widgets"
/>
</CustomersDataSet>
ReadXml
In addition to being able to extract XML from a DataSet, you can also load XML
data into a DataSet using the ReadXml method.
Here are the various overloads of the ReadXml method:
public
void
ReadXml(Stream);
public
void
ReadXml(Stream,
XmlReadMode);
public
void
ReadXml(string);
public
void
ReadXml(string,
XmlReadMode);
public
void
ReadXml(TextReader);
public
void
ReadXml(TextReader,
XmlReadMode);
public
void
ReadXml(XmlReader);
public
void
ReadXml(XmlReader,
XmlReadMode);
The values for the XmlReadMode enumeration are:
| Value |
Description |
| Auto |
The default. Will use DiffGram, ReadSchema, or InferSchema mode as appropriate. |
| DiffGram |
Reads a diffgram and applies the changes to the dataset. |
| Fragment |
Reads xml documents, such as those generated by FOR XML sql queries. |
| IgnoreSchema |
Ignores any schema in the XML file, and loads data using the current schema in
the DataSet. Used when the DataSet is already configured with a schema. |
| InferSchema |
Determines the schema based on the format of the data, and loads the data. Used
when neither the DataSet nor the XML file has a schema. |
| ReadSchema |
Reads the schema in the XML file to configure the DataSet, and loads the data. |
Using XPath to Query XML Data
The XPath 1.0 specification is implemented by classes in the System.Xml.XPath
namespace. There are 5 core classes in this namespace, as described below:
| Class |
Description |
| XPathDocument |
Provides a fast, read-only cache for XML document processing using XSLT.
|
| XPathException |
The exception that is thrown when an error occurs when processing an XPath
expression.
|
| XPathExpression |
Encapsulates a compiled XPath expression. This class is returned as a result of
a call to Compile and is used by the Select, Evaluate and Matches methods.
|
| XPathNavigator |
Reads data from any data store using a cursor model.
|
| XPathNodeIterator |
Provides an iterator over a set of selected nodes.
|
The key class is the XPathNavigator, which you obtain from an XmlDocument,
XmlDataDocument, or XPathDocument and use to issue XPath queries against the
XML data. Consider the following example XML file, which tracks registrations
to a fictional conference :
<registrants
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <registrant
FirstName="Bilbo"
LastName="Baggins"
/>
 <registrant
FirstName="Frodo"
LastName="Baggins"
/>
 <registrant
FirstName="Gandalf"
LastName="TheWhite"
/>
</registrants>
Now let us look at an example console application that utilizes the
XPathNavigator object to select the nodes where the LastName attribute is
"Baggins". First, we create an XmlDocument object, and load our XML file. Next,
we use the CreateNavigator method to obtain an instance of an XPathNavigator
for the document. We use the Select method of this object to issue an XPath
query. The return value is an XPathIterator object that we can use to iterate
through the nodes that matched our XPath query. The full code for the console
based application is below.
using
System;
using
System.IO;
using
System.Xml;
using
System.Xml.Schema;
using
System.Xml.XPath;
namespace
LearningXml
{
 class
XpathExample
 {
   static
void
Main(string[]
args)
   {
           XmlDocument
objXmlDoc
=
new
XmlDocument();
     objXmlDoc.Load("books.xml");
           XPathNavigator
xp
=
objXmlDoc.CreateNavigator();
           XPathNodeIterator
objIterator
=
xp.Select("registrants/registrant[@LastName='Baggins']");
     
           while(objIterator.MoveNext())
     {
       Console.WriteLine("Found Element: "
+
objIterator.Current.LocalName);
       Console.WriteLine(" First Name: "
+
objIterator.Current.GetAttribute("FirstName",
""));
       Console.WriteLine("  Last Name: "
+
objIterator.Current.GetAttribute("LastName",
""));
     }
   }
 }
}
When run at the command line, you can see that the two nodes selected were the
Baggins: Frodo and Bilbo.
Retrieving XML from SQL Server
With the release of SQL Server 2000, Microsoft has added functionality to
directly retrieve data from SQL Server in XML using the FOR XML extension to
the SELECT statement. An example query on the Northwind sample database would
look like this:
SELECT *
FROM Employees
FOR XML auto
You can execute and retrieve XML data through ADO.NET by using the
ExecuteXmlReader function of the Command object. This method returns an
XmlReader object. The following console application executes a SQL statement
against the Northwind sample database to retrieve all employees in XML format,
and writes the XML to the console:
using
System;
using
System.Data.SqlClient;
using
System.Data;
using
System.Xml;
namespace
XmlQueryTest
{
class
Class1
{
static
void
Main(string[]
args)
{
string
connstring
=
"Server=localhost;Database=Northwind;UID=sa;Password=;";
SqlConnection
objConn
=
new
SqlConnection(connstring);
SqlCommand
objCmd
=
new
SqlCommand("SELECT * FROM Employees FOR XML AUTO",
objConn);
objConn.Open();
XmlReader
objXmlReader
=
objCmd.ExecuteXmlReader();
objConn.Close();
objXmlReader.Read();
Console.Write(objXmlReader.ReadOuterXml());
}
}
}
Here is the output showing our query results in XML:
Validating an XML Document
When loading XML and XSD information into a DataSet, or when loading XML into
an XmlDocument object the XML document is not automatically validated against
the schema. For those cases where you want to validate an XML document, you
need the XmlValidatingReader class.
Despite the name, the XmlValidatingReader isn't a reader on it's own. Rather,
it supplements and attaches to a reader, such as the XmlTextReader, to provide
the validation service. When a reader such as the XmlTextReader loads an XML
Document, it first checks to see if the document is well-formed. With an
XmlValidatingReader attached, it next looks for the presence of a Schema or
DTD, and validates the XML against that schema. Errors encountered during
validating prompt a Validation event, which is passed a ValidationEventHandler
argument containing the details about where the validation failed. The default
event handler throws exceptions when validation errors occur, or you can create
your own event handler for custom error handling.
Let's look at how to create and use an XmlValidatingReader.
-
Setup XmlTextReader of FileStream with XML data
-
Create XmlValidating Reader, and set Schema properties
-
Setup Callback function, and attach to Validation Event Handler collection
-
Loop through and Read() the XML file
The following example shows these steps.
using
System;
using
System.IO;
using
System.Xml;
using
System.Xml.Schema;
namespace
ValidationSample
{
class
Sample
{
public
static
void
Main()
{
XmlTextReader
tr
=
new
XmlTextReader("books.xml");
XmlValidatingReader
vr
=
new
XmlValidatingReader(tr);
vr.ValidationType
=
ValidationType.Schema;
vr.ValidationEventHandler
+=
new
ValidationEventHandler(ValidationCallBack);
while(vr.Read())
{}
}
public
static
void
ValidationCallBack(object
sender,
ValidationEventArgs
args)
{
Console.WriteLine("Validation Error");
Console.WriteLine(" Severity :{0}",
args.Severity);
Console.WriteLine(" Message :{0}",
args.Message);
}
}
}
The books.xml file referenced in the example contains the following. Notice how
the price element contains a string, and not a decimal.
<library
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="books.xsd">
<book>
<title>Life
in
the
Programmer's World</title>
<isbn>2928349289393</isbn>
<price>12
dollars</price>
</book>
</library>
The books.xsd schema referenced by the books.xml file contains the following:
<?xml
version="1.0"
encoding="UTF-8"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<xs:element
name="library">
<xs:complexType>
<xs:sequence>
<xs:element
ref="book"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element
name="book">
<xs:complexType>
<xs:sequence>
<xs:element
ref="title"/>
<xs:element
ref="isbn"/>
<xs:element
ref="price"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element
name="isbn"
type="xs:long"/>
<xs:element
name="price"
type="xs:decimal"/>
<xs:element
name="title"
type="xs:string"/>
</xs:schema>
Notice how our Schema dictates a decimal for the <price> element, but our
XML document has a string value. Using this XML file in our validation example
results in the reporting of the error: