Reading and Writing XML Files
The abstract XmlReader and XmlWriter classes of the System.XML namespace provide reading and writing functionality for XML files. XmlReaders are implemented in three main classes that provide forward-only processing of XML files. Table 12.3 lists the implementations of the XmlReader class.
Class | Description |
---|---|
XmlTextReader | Fastest implementation of XmlReader. It checks for well-formed XML, but doesn't support data validation. This reader cannot expand general entities and doesn't support default attributes. |
XmlValidatingReader | Implementation of XmlReader that can validate data using DTDs or schemas. This reader can also expand general entities and supports default attributes. |
XmlNodeReader | Implementation of XmlReader that reads XML data from an XmlNode. |
Creating the XMLCode Test Form
To the XML application you've been working with, add a new form named XMLCode by right-clicking your project name, selecting Add from the contextual menu, and then selecting Add New Form. Table 12.4 describes the controls and the Text and Name properties you must add to the form. When you're done, your form should look like Figure 12.12.
Figure 12.12. XMLCode form after adding controls.

Control | Name | Text |
---|---|---|
CommandButton | GetXmlFromDataSet | Get XML From DataSet |
CommandButton | GetXmlDataReader | Get XML DataReader |
CommandButton | LoadXmlFromFile | Load XML From File |
CommandButton | SimpleXmlTextReader | Simple XmlTextReader |
CommandButton | XmlTextReaderByNode | XmlTextReader By Node |
CommandButton | XmlTextReaderAttributes | XmlTextReader Attributes |
CommandButton | UsingXmlDocument | Using XmlDocument |
CommandButton | XPathSelectNodes | XPath SelectNodes |
CommandButton | DOMXmlAttributes | DOM XmlAttributes |
RichTextBox | XmlOut | Leave Blank |
- System.Data.SqlClient
- System.IO
- System.Text
- System.Xml
Now that the form is created, you'll write code to create the XML files you'll be working with.
Using a DataSet to Create XML Files and Schemas
Earlier today you created an XML file based on rules you defined in an XML schema file. I mentioned that this could have been done just as easily using methods of the DataSet class. To create an XML file and schema based on the Customers table in the Northwind database, double-click the Get XML from DataSet button and add the code in Listing 12.3.
Listing 12.3 Using the WriteXml and WriteXmlSchema Methods of the DataSet Class

Private Sub GetXmlFromDataSet_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles GetXmlFromDataSet.Click
' Check to see if the files exist, is they do, Delete them
If File.Exists("Customers.xml") Then
File.Delete("Customers.xml")
End If
If File.Exists("CustomersSchema.xml") Then
File.Delete("CustomersSchema.xml")
End If
' Create a DataSet to hold the XML
Dim ds As DataSet = New DataSet("NorthwindCustomers")
' Connect to the database
Dim cn As SqlConnection = New SqlConnection( _
"database=northwind;server=.\NetSDK;Integrated Security=SSPI")
' Set the DataAdapter to get the data
Dim adp As New SqlDataAdapter("Select * from Customers", cn)
' Fill the DataSet with the DataAdapter command
adp.Fill(ds, "Customer")
' call the WriteXml and WriteXmlSchema methods
ds.WriteXml("Customers.xml")
ds.WriteXmlSchema("CustomersSchema.xml")
End Sub

Run the application and execute the code you just wrote. If you drill into the Bin directory of the solution, you'll see the two new XML files that your code created. Using the WriteXml and WriteXmlSchema methods of the DataSet class, you created the XML file and XML schema for the Customers table in the Northwind database. If you double-click the XML files in the Solution Explorer, you'll see the XML that was created in the XML designer. You'll notice that using the WriteXml and WriteXmlSchema methods creates XML files using Elementsthere are no attributes in the created files.To create attribute-based XML files, you can use a DataReader with the For XML Auto clause in T-SQL and a FileStream to generate the XML file.
private void GetXmlFromDataSet_Click(object sender, System.EventArgs e)
{
// Check to see if the files exist, is they do, Delete them
if (File.Exists(@"Customers.xml"))
{
File.Delete(@"Customers.xml");
}
if (File.Exists(@"CustomersSchema.xml"))
{
File.Delete(@"CustomersSchema.xml");
}
// Create a DataSet to hold the XML
DataSet ds = new DataSet("NorthwindCustomers");
// Connect to the database
SqlConnection cn = new SqlConnection
(@"database=northwind;server=.\NetSDK;Integrated Security=SSPI");
// Set the DataAdapter to get the data
SqlDataAdapter adp = new SqlDataAdapter("Select * from Customers", cn);
// Fill the DataSet with the DataAdapter command
adp.Fill(ds, "Customer");
// call the WriteXml and WriteXmlSchema methods
ds.WriteXml(@"Customers.xml");
ds.WriteXmlSchema(@"CustomersSchema.xml");
}
Using For XML Auto to Create Attribute-Based XML Files
To create an attribute-based XML file, you can use many techniques. The XmlTextWriter class could be used, but there's an easier way. Listing 12.4 demonstrates how to use the For XML Auto Transact-SQL clause to return XML from SQL Server. After the data is returned in a SqlDataReader, you can loop through the data and write it out to a stream. To ensure that the resulting XML is well formed, you add the prolog information and the root element's start and end tags. Double-click on the Get XML DataReader button and add the code in Listing 12.4.
Listing 12.4 Creating an Attribute-Based XML File

Private Sub GetXmlDataReader_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles GetXmlDataReader.Click
' Create a stream to write to
Dim sr As New StreamWriter("CustomerAttributes.xml")
' Add the XML prolog and Root element
sr.WriteLine("<?xml version='1.0' standalone='yes' ?>")
sr.WriteLine("<TransformedCustomers>")
' Connect to the database
Dim cn As SqlConnection = New SqlConnection( _
"database=northwind;server=.\NetSDK;Integrated Security=SSPI")
cn.Open()
' Use the For XML Auto clause
Dim cmd As New SqlCommand("Select * from customers for XML Auto", cn)
' Declare the DataReader to read the data
Dim dr As SqlDataReader = cmd.ExecuteReader
' Loop thru the DataReader, writing out the file
While dr.Read
' Get each chunk of XML as the reader reads it
sr.Write(dr(0))
End While
' Write the End Element
sr.WriteLine("</TransformedCustomers>")
' Flush and Close the Stream
sr.Flush()
sr.Close()
End Sub

If you run the application now and run the GetXMLDataReader code, you'll have a new XML file in your Bin directory named CustomerAttributes.xml. If you compare this file to the Customers.xml file you created in Listing 12.3, you'll see the XML format looks quite different, but the way they are handled in the Data view of the XML Designer is exactly the same. They're both examples of well-formed XML documents.The amount of actual text, or size in bytes, of an XML file that's purely using elements is much larger than an XML file created using attributes. If the amount of data being passed over the wire is critical, you should consider using attributes for flat XML files instead of elements.The ReadXml method of the DataSet you used earlier to load the Employees.xml file behaves the same for XML documents based on elements or attributes.To read the complete XML file as a stream, you can use the GetXml method of the DataSet class. Listing 12.5 loads the Customers.xml file into a DataSet, and then uses the GetXml method of the DataSet class to display the data in the textbox. Add the code in Listing 12.5 to the LoadXml click event.
private void GetXmlDataReader_Click(object sender, System.EventArgs e)
{
// Create a stream to write to
StreamWriter sr = new StreamWriter(@"CustomerAttributes.xml");
// Add the XML prolog and Root element
sr.WriteLine("<?xml version='1.0' standalone='yes' ?>");
sr.WriteLine("<TransformedCustomers>");
// Connect to the database
SqlConnection cn = new SqlConnection
(@"database=northwind;server=.\NetSDK;Integrated Security=SSPI");
cn.Open();
// Use the For XML Auto clause
SqlCommand cmd = new SqlCommand
(@"Select * from customers for XML Auto", cn);
// Declare the DataReader to read the data
SqlDataReader dr = cmd.ExecuteReader();
// Loop thru the DataReader, writing out the file
while (dr.Read())
{
// Get each chunk of XML as the reader reads it
sr.Write(dr.GetString (0));
}
// Write the End Element
sr.WriteLine("</TransformedCustomers>");
// Flush and Close the Stream
sr.Flush();
sr.Close();
}
Listing 12.5 Loading XML to a String Using the ReadXml and GetXml Methods of the DataSet Class

Private Sub LoadXMLFile_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles LoadXMLFile.Click
' Create a new DataSet
Dim ds As New DataSet()
' Load the XML file into the DataSet with the ReadXml method
ds.ReadXml("Customers.xml")
' Send the XML to the textbox
XmlOut.Text = ds.GetXml()
End Sub

In Listing 12.5, the ReadXml method is used to fill the DataSet with the data from the XML file. After the DataSet is loaded, the GetXml method is called to output the XML data to the text box. When you run the application and execute the code, the data from the XML file is simply displayed in the text box, and you should see something similar to Figure 12.13.
private void LoadXMLFile_Click(object sender, System.EventArgs e)
{
// Create a new DataSet
DataSet ds = new DataSet();
// Load the XML file into the DataSet with the ReadXml method
ds.ReadXml(@"Customers.xml");
// Send the XML to the textbox
XmlOut.Text = ds.GetXml();
}
Figure 12.13. Output from the GetXml method of the DataSet class.

After an XML file is loaded into the DataSet, you can treat it like any other DataTable in the DataSet. You can loop through tables, rows, and columns just as you did over the last two days when you learned how to work with DataSets in ADO.NET. Remember that XML is data. After data is in a DataSet, the methods to interact with it are the same no matter what the datasource is.
Using the XmlTextReader Class
The XmlTextReader class can be compared to the DataReader class of the System.Data namespace. DataReaders are read-only, forward-only unbuffered streams of data from a database. XmlReaders are read-only, forward-only unbuffered streams of data from XML files. The XmlTextReader class provides robust functionality for reading XML files that are element based or attribute based.The Read method of the XmlTextReader class reads the XML stream until a False value is returned to indicate the end of the file has been reached. While reading the file, you can check the NodeType property to determine what node you're reading and what kind of node it is. Table 12.5 lists the NodeType enumeration and descriptions of the different types of nodes.
Listing 12.6 Implementing a Simple XmlTextReader and Checking the NodeType Property

Private Sub SimpleXmlTextReader_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles SimpleXmlTextReader.Click
' Load the XML file into a new XmlTextReader
Dim xr As New XmlTextReader("Customers.xml")
Dim sb As New StringBuilder()
' Read the file, getting the Text nodes, which is the actual data
While xr.Read
' Check the XmlNodeType enumeration for Text
If xr.NodeType = XmlNodeType.Text Then
' Get the Value of the Text nodetype
sb.Append(xr.Value)
' Append the carriage return
sb.Append(ControlChars.CrLf)
End If
End While
' Output to the textbox
XmlOut.Text = sb.ToString
' Close the XmlTextReader
xr.Close()
End Sub

After you call the Read method in Listing 12.6, you check the NodeType property. If NodeType is equal to NodeType.Text, the value is appended to the string builder. If you run the application and execute the code, you'll see something similar to Figure 12.14.
private void SimpleXmlTextReader_Click
(object sender, System.EventArgs e)
{
// Load the XML file into a new XmlTextReader
XmlTextReader xr = new XmlTextReader(@"Customers.xml");
StringBuilder sb = new StringBuilder();
// Read the file, getting the Text nodes, which is the actual data
while (xr.Read())
{
// Check the XmlNodeType enumeration for Text
if (xr.NodeType == XmlNodeType.Text)
{
// Get the Value of the Text nodetype
sb.Append(xr.Value + "\n");
}
}
// Output to the textbox
XmlOut.Text = sb.ToString();
// Close the XmlTextReader
xr.Close();
}
Figure 12.14. The XmlReader outputting only Text NodeTypes.

By outputting only the Text node type, you're getting rid of the XML tags that describe the data.If you need to output specific data, such as the CompanyName, you can check the Name property of the current element. The code in Listing 12.7 reads each element, and adds only the CompanyName values to the string builder for output to the text box. Add the code in Listing 12.7 to the XmlTextReaderByNode click event.
Listing 12.7 Reading XML Elements by Name

Private Sub XmlTextReaderByNode_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles XmlTextReaderByNode.Click
' Load the XML File
Dim xr As New XmlTextReader("Customers.xml")
Dim sb As New StringBuilder()
' Retrieve data based on a specific Node Name
While xr.Read
' Ignore any non-character data
If xr.ReadString.Length > 0 Then
' Check the name of the Element
If xr.Name = "CompanyName" Then
' Read the String with the ReadString() method
sb.Append(xr.ReadString())
' Append the carriage return
sb.Append(ControlChars.CrLf)
End If
End If
End While
' Display in the textbox
XmlOut.Text = sb.ToString
End Sub

When you run this code, you get only the CompanyName values from the XML file, as Figure 12.15 demonstrates.
private void XmlTextReaderByNode_Click
(object sender, System.EventArgs e)
{
// Load the XML file into a new XmlTextReader
XmlTextReader xr = new XmlTextReader(@"Customers.xml");
StringBuilder sb = new StringBuilder();
// Read the file, getting the Text nodes, which is the actual data
while (xr.Read())
{
// Ignore any non-character data
if (xr.ReadString().Length > 0)
{
// Check the name of the Element
if (xr.Name == "CompanyName")
{
// Read the String with the ReadString() method
sb.Append(xr.ReadString() + "\n");
}
}
}
// Output to the textbox
XmlOut.Text = sb.ToString();
// Close the XmlTextReader
xr.Close();
}
Figure 12.15. Retrieving elements by name using the XmlTextReader class.

Using the Name property along the with NodeType is extremely useful for getting data values for specific elements.
Working with Attributes and the XmlTextReader
Until now, you've used only the element-based Customers.xml file. But you'll often need to deal with attributes in an XML file. Using the GetAttribute method of the XmlTextReader class returns a specific attribute in a node.When the reader reads a node using the Read method, it reads all the information in the node. So, if there are attributes in the node, it looks at them as a collection. By checking the HasAttributes and AttributesCount properties, you can determine whether a node has attributes, and use the GetAttributes method to grab the data values for the attributes you're looking for.Listing 12.8 uses the GetAttributes method to accomplish the same thing that Listing 12.7 did: retrieve CompanyName. The difference is that the XML file you're reading is the attribute-based file, not the element-based file. The code iterates through all the attributes for each node and outputs them to the text box. Add the code in Listing 12.8 to the XmlTextReaderAttributes click event.
Listing 12.8 Using the GetAttributes Method

Private Sub XmlTextReaderAttributes_Click
(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles XmlTextReaderAttributes.Click
' Load the XML file into a new XmlTextReader
Dim xr As New XmlTextReader("CustomerAttributes.xml")
Dim sb As New StringBuilder()
' Read the file
While xr.Read
' Only look at Elements
If xr.NodeType = XmlNodeType.Element Then
' That have attributes
If xr.HasAttributes Then
' With an AttributeCount > 0
If xr.AttributeCount > 0 Then
sb.Append(ControlChars.CrLf)
' Write out the CompanyName
sb.Append(xr.GetAttribute("CompanyName"))
sb.Append(ControlChars.CrLf)
' Loop thru the Attributes and write them out
Dim intX As Integer
For intX = 0 To xr.AttributeCount - 1
sb.Append("***")
' Get the Attribute
sb.Append(xr.GetAttribute(intX))
sb.Append(ControlChars.CrLf)
Next
End If
End If
End If
End While
' Output to the textbox
XmlOut.Text = sb.ToString
'Console.WriteLine(sb.ToString)
' Close the XmlTextReader
xr.Close()
End Sub

The code in Listing 12.8 first checks to see whether the current node has attributes. If it does, the code makes sure that the count is greater then zero, and then loops through the attributes using the index of the attribute within the node. If you run this now and execute the code, you'll see something like Figure 12.16.
private void XmlTextReaderAttributes_Click
(object sender, System.EventArgs e)
{
// Load the XML file into a new XmlTextReader
XmlTextReader xr = new XmlTextReader(@"CustomerAttributes.xml");
StringBuilder sb = new StringBuilder();
// Read the file, getting the Text nodes, which is the actual data
while (xr.Read())
{
// Only look at Elements
if (xr.NodeType == XmlNodeType.Element)
{
// That have attributes
if (xr.HasAttributes)
{
// With an AttributeCount > 0
if (xr.AttributeCount > 0)
{
// Write out the CompanyName
sb.Append(xr.GetAttribute("CompanyName"));
for (int i = 0; i < xr.AttributeCount; i++)
{
sb.Append("***");
sb.Append(xr.GetAttribute(i) + "\n");
}
}
}
}
}
// Output to the textbox
XmlOut.Text = sb.ToString();
// Close the XmlTextReader
xr.Close();
}
Figure 12.16. Using attributes with the XmlTextReader class.

Reading attributes is just as easy as reading elements in an XML file. More complex XML files use a combination of elements and attributes to describe the data, so understanding how to work with each type of node is important.