.netCoders Contact Us
Search:

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)
        {
            //Load XML File
            XmlDocument objXmlDoc = new XmlDocument();
            objXmlDoc.Load("books.xml");

            //Get Registrant Nodes
            XmlNodeList objNodes = objXmlDoc.GetElementsByTagName("registrant");

            //Loop through Selected Nodes
            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:

//Writes the current data for the DataSet using the specified System.IO.Stream.
public void WriteXml(Stream);
public void WriteXml(Stream, XmlWriteMode);

//Writes the current data for the DataSet to the specified file.
public void WriteXml(string);
public void WriteXml(string, XmlWriteMode);

//Writes the current data for the DataSet using the specified TextWriter.
public void WriteXml(TextWriter);
public void WriteXml(TextWriter, XmlWriteMode);

//Writes the current data for the DataSet to the specified XmlWriter.
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:
//Setting DataSet name at Instantiation
DataSet objDataSet = new DataSet("CustomersDataSet");

//Setting DataSet name after Instantiation
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:
//Setting Table Name when Filling
SqlDataAdapter da = new SqlDataAdapter(objCmd);
da.Fill(objDataSet, "CustomersTable");

//Setting Table Name after DataSet has been Filled
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:

//Indicate we want columns mapped as XML attributes
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:

//Reads XML and schema into the DataSet using the specified System.IO.Stream.
public void ReadXml(Stream);
public void ReadXml(Stream, XmlReadMode);

//Reads XML and schema into the DataSet from the specified file.
public void ReadXml(string);
public void ReadXml(string, XmlReadMode);

//Reads XML and schema into the DataSet using the specified System.IO.TextReader.
public void ReadXml(TextReader);
public void ReadXml(TextReader, XmlReadMode);

//Reads XML and schema into the DataSet using the specified System.Xml.XmlReader.
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)
        {
            //Load XML File
            XmlDocument objXmlDoc = new XmlDocument();
            objXmlDoc.Load("books.xml");

            //Get XPathNavigator object
            XPathNavigator xp = objXmlDoc.CreateNavigator();

            //Select Nodes with LastName="Baggins"
            XPathNodeIterator objIterator = xp.Select("registrants/registrant[@LastName='Baggins']");
            
            //Loop through Selected Nodes
            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)
        {
            //Create Connection
            string connstring = "Server=localhost;Database=Northwind;UID=sa;Password=;";
            SqlConnection objConn = new SqlConnection(connstring);
            
            //Create Command with SQL Query using FOR XML AUTO extension
            SqlCommand objCmd = new SqlCommand("SELECT * FROM Employees FOR XML AUTO", objConn);

            //Execute Command to get XML
            objConn.Open();
            XmlReader objXmlReader = objCmd.ExecuteXmlReader();
            objConn.Close();
        
            //Output XML to console
            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.

  1. Setup XmlTextReader of FileStream with XML data
  2. Create XmlValidating Reader, and set Schema properties
  3. Setup Callback function, and attach to Validation Event Handler collection
  4. 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()
    {
        //Open XML File with Reader
        XmlTextReader tr = new XmlTextReader("books.xml");

        //Attach ValidatingReader
        XmlValidatingReader vr = new XmlValidatingReader(tr);
        vr.ValidationType = ValidationType.Schema;

        //Set Callback Function
        vr.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);

        //Read and Validate
        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: