Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1
.pdf558 C H A P T E R 1 2 ■ A D O . N E T A N D D A T A B A S E D E V E L O P M E N T
Done! Figure 12-21 shows the final results of the second example.
Figure 12-21. Maintaining authors using a DataGridView
Summary
In this chapter, you saw a large portion of the .NET Framework’s ADO.NET. You started out by covering the basics of ADO.NET. You then moved on to creating a database to work with through the rest of the chapter using Visual Studio 2005. Next, you explored how to connect, query, insert, update, delete, count, and sum rows of a database using connected access to the database. Finally, you learned how to do the same things with disconnected access, in the process building a couple simple Windows Forms author maintenance tools.
You have now learned the code you will need to implement ADO.NET in either a connected or disconnected manner. The world of databases should now be open to you when you create your applications.
In the next chapter, you’ll examine the mysterious world of XML, the last of the four common methods of getting input into and out of your .NET Windows applications.
560 C H A P T E R 1 3 ■ X M L
An attribute is an extension to the start element node. It provides more information about the element. Attributes are one or more name= "value" pairs added after the element text name but before the closing angle bracket: <Element_Tag name="value" >.
Two additional components that you will encounter are the XML header declaration and the comment. The header declaration indicates that the file should be parsed as XML and in most cases will simply read
<?xml version="1.0" encoding="utf-8"?>
Comments provide the reader of the XML file additional information that will be ignored by the XML parser. The syntax of a comment is <!-- comment_text -->.
Listing 13-1 shows the XML document that you’ll be using throughout the chapter.
Listing 13-1. An XML Monster File
<?xml version="1.0" encoding="utf-8"?> <!-- Monster List -->
<MonsterList>
<!-- Easy Monster --> <Monster>
<Name>Goblin</Name>
<HitDice Dice="1d8" Default="4"/>
<Weapon Number="1" Damage="1d4">Dagger</Weapon> </Monster>
<!-- Medium Monster --> <Monster>
<Name>Succubus</Name>
<HitDice Dice="6d8+6" Default="33"/>
<Weapon Number="2" Damage="1d3+1">Claw</Weapon> <Weapon Number="1" Damage="1d4">Dagger</Weapon>
</Monster>
<!-- Tough Monster --> <Monster>
<Name>Red Dragon</Name>
<HitDice Dice="22d12+110" Default="253"/> <Weapon Number="1" Damage="2d8">Bite</Weapon> <Weapon Number="2" Damage="2d6">Claw</Weapon> <Weapon Number="2" Damage="1d8">Wing</Weapon>
</Monster>
</MonsterList>
The .NET Framework XML Implementations
The .NET Framework class library provides two ways of processing XML data:
•Fast, noncached, forward-only stream
•Random access via an in-memory Document Object Model (DOM) tree
Both methods of processing XML data are equally valid. However, each has a definite time when it is better suited. At other times, both will work equally well, and the decision of which to use is up to the developer’s taste.
C H A P T E R 1 3 ■ X M L |
561 |
The major deciding factors for choosing one method over the other are whether all data needs to be in memory at one time (large files take up large amounts of memory, which in many cases isn’t a good thing, but with the large amount of physical memory a computer can have nowadays the size of the XML file is almost irrelevant) and whether random access to the data is needed. When either of these factors occurs, the DOM tree should probably be used because the process of repeatedly starting from the beginning of the document and reading forward sequentially through it to find the right place in the stream of XML to read, update, or write random data is time consuming.
On the other hand, if the data can be processed sequentially, a forward-only stream is probably the better choice because it is easier to develop and uses fewer resources more efficiently than a DOM tree. However, there is nothing stopping you from using a DOM tree in this scenario as well.
Implementing code to process XML with the .NET Framework class library requires referencing the System.Xml.dll assembly. You would think that due to the heavy reliance on XML in the .NET Framework, it would be part of the mscorlib.dll assembly. Because it is not, your source code implementing XML requires the following code be placed at the top of your source code (this is done automatically for you by Visual Studio 2005 for Windows Forms applications but not for console applications):
#using <system.xml.dll>
Six namespaces house all of the XML functionality within the .NET Framework class library. Table 13-1 describes these namespaces at a high level.
Table 13-1. XML Namespaces
Namespace |
Description |
System::Xml |
Provides the core of all XML functionality |
System::Xml::Schema |
Provides support for XML Schema definition language |
|
(XSD) schemas |
System::Xml::Serialization |
Provides support for serializing objects into XML |
|
formatted documents or streams |
System::Xml::XPath |
Provides support for the XPath parser and |
|
evaluation engine |
System::Xml::Xsl |
Provides support for Extensible Stylesheet Language |
|
Transformations (XSLT) transforms |
|
|
Forward-Only Access
Forward-only access to XML is amazingly fast. If you can live with the restriction that you can process the XML data only in a forward-only method, then this is the way to go. The base abstract classes for implementing this method of access are named, intuitively enough, XmlReader and
XmlWriter.
The .NET Framework class library’s implementation of forward-only access, when you first look at it, seems a lot like the Simple API for XML (SAX), but actually they are fundamentally different. Whereas SAX uses a more complex push model, the class library uses a simple pull model. This means that a developer requests or pulls data one record at a time instead of having to capture the data using event handlers.
562 C H A P T E R 1 3 ■ X M L
Coding using the .NET Framework class library’s implementation of forward-only access seems, to me, more intuitive because you can handle the processing of an XML document as you would a simple file, using a good old-fashioned while loop. There is no need to learn about event handlers or SAX’s complex state machine.
Reading from an XML File
To implement forward-only reading of an XML file you use the XmlReader class. This is a little tricky as the XmlReader is an abstract class. This means, instead of the normal creation of the class using a constructor, you use the static method Create(), in conjunction with the optional
XmlReaderSettings class.
For those of you who developed XML code with a previous version of the .NET Framework, it is also possible to still use the XmlTextReader and XmlNodeReader classes, which inherit from XmlReader. With .NET Framework version 2.0, Microsoft has recommended the use of the XmlReader class, since using the Create() method with the XmlReaderSettings class you get the following benefits:
•The ability to specify the features you want supported by the created XmlReader instance.
•The ability to create an instance of XmlReaderSettings that can be reused to create multiple XmlReaders, each sharing the same features.
•The ability to create a unique instance or modify an existing instance of the XmlReaderSettings, allowing each XmlReader to have a different set of features.
•You can extend the features of the XmlReader. The Create() method can accept another XmlReader. The underlying XmlReader instance can be a reader such as an XmlTextReader, or another user-defined XmlReader instance that you add your own features to.
•Provides the ability to take advantage of all the new features added to the XmlReader class in .NET Framework version 2.0. Some features, such as better conformance checking and compliance to the XML 1.0 recommendation, are only available with XmlReader instances created using the Create() method.
I’ll cover the XmlReaderSettings class when I discuss XML file validation later in the chapter, as much of this class pertains to validation.
The XmlReader class is made up of a number of properties and methods. Some of the more common properties you will probably encounter are as follows:
•AttributeCount is an Int32 that specifies the number of attributes in the current Element, DocumentType, or XmlDeclaration node. Other node types don’t have attributes.
•Depth is an Int32 that specifies the depth of the current node in the tree.
•EOF is a Boolean that’s true if the reader is at the end of the file; otherwise, it’s false.
•HasAttributes is a Boolean that’s true if the current node has attributes; otherwise, it’s false.
•HasValue is a Boolean that’s true if the current node has a value; otherwise, it’s false.
•IsEmptyElement is a Boolean that’s true if the current node is an empty element, or in other words, the element ends in />.
C H A P T E R 1 3 ■ X M L |
563 |
•Item is the String value of an attribute specified by index or name within the current node.
•LocalName is the String the local name of the current node. For example, Monster is the
LocalName for the element <my:Monster>.
•Name is the String qualified name of the current node. For example, the fully qualified my:Monster is the Name for the element <my:Monster>.
•NodeType is an XmlNodeType enum class that represents the node type (see Table 13-2) of the current node.
•Prefix is the String namespace prefix of the current node. For example, my is the namespace for the element <my:Monster>.
•ReadState is a ReadState enum class of the current state of the XmlReader object. Possible states are: Closed, EndOfFile, Error, Initial, and Interactive.
•Value is the String value for the current node.
Here are a few of the more common XmlReader methods:
•Close() changes the ReadState of the reader to Closed.
•Create() is used to create an instance of an XmlReader.
•GetAttribute() gets the String value of the attribute.
•IsStartElement() returns the Boolean true if the current node is a start element tag.
•MoveToAttribute() moves to a specified attribute.
•MoveToContent() moves to the next node containing content.
•MoveToElement() moves to the element containing the current attribute.
•MoveToFirstAttribute() moves to the first attribute.
•MoveToNextAttribute() moves to the next attribute.
•Read() reads the next node.
•ReadAttributeValue() reads an attribute containing entities.
•ReadContentAs[data type]() reads the current content of the node as the [data type] specified. Examples are ReadContentAsInt() and ReadContentAsDouble().
•ReadElementContentAs[data type]() reads the value of element node as the [data type] specified. Examples are ReadElementContentAsInt() and ReadElementContentAsDouble().
•ReadElementString() is a helper method for reading simple text elements.
•ReadEndElement() verifies that the current node is an end element tag and then reads the next node.
•ReadStartElement() verifies that the current node is a start element tag and then reads the next node.
•ReadString() reads the contents of an element or text node as a String.
•Skip() skips the children of the current node.
564 C H A P T E R 1 3 ■ X M L
The XmlReader class processes an XML document by tokenizing a text stream of XML data. Each token (or node, as it is known in XML) is then made available by the Read() method and can be handled as the application sees fit. A number of different nodes are available, as you can see in Table 13-2.
Table 13-2. Common XML Node Types
Node Type |
Description |
Attribute |
An element attribute |
Comment |
A comment |
Document |
The root of a document tree providing access to the entire XML |
|
document |
DocumentFragment |
A subtree of a document |
DocumentType |
A document type declaration |
Element |
A start element tag |
EndElement |
An end element tag |
EndEntity |
The end of an entity declaration |
Entity |
The start of an entity declaration |
EntityReference |
A reference to an entity |
None |
The value placed in NodeType before any Read() method is called |
SignificantWhitespace |
White space between markups in a mixed content model or white |
|
space within the xml:space="preserve" scope |
Text |
The text content |
Whitespace |
White space between markups |
XmlDeclaration |
An XML declaration |
|
|
The basic logic of implementing the XmlReader class is very similar to that of a file IO class:
1.Open the XML document.
2.Read the XML element.
3.Process the element.
4.Repeat steps 2 and 3 until the end of file (EOF) is reached.
5.Close the XML document.
The example in Listing 13-2 shows how to process the previous XML monster file. The output is to the console and contains a breakdown of the nodes that make up the XML file.
C H A P T E R 1 3 ■ X M L |
565 |
Listing 13-2. Splitting the XML Monster File into Nodes
#using <system.xml.dll>
using namespace System; using namespace System::Xml;
String ^indent(Int32 depth)
{
String ^ind = "";
return ind->PadLeft(depth * 3, ' ');
}
void main()
{
XmlReader ^reader;
try
{
reader = XmlReader::Create("Monsters.xml");
while (reader->Read())
{
switch (reader->NodeType)
{
case XmlNodeType::Comment: Console::WriteLine(
"{0}Comment node: Value='{1}'", indent(reader->Depth), reader->Value);
break;
case XmlNodeType::Element: Console::WriteLine(
"{0}Element node: Name='{1}'", indent(reader->Depth), reader->Name);
if (reader->HasAttributes)
{
while (reader->MoveToNextAttribute())
{
Console::WriteLine(
"{0}Attribute node: Name='{1}' Value='{2}'", indent(reader->Depth), reader->Name, reader->Value);
}
reader->MoveToElement();
}
566 C H A P T E R 1 3 ■ X M L
if (reader->IsEmptyElement)
{
Console::WriteLine(
"{0}End Element node: Name='{1}'", indent(reader->Depth), reader->Name);
}
break;
case XmlNodeType::EndElement: Console::WriteLine(
"{0}End Element node: Name='{1}'", indent(reader->Depth), reader->Name);
break;
case XmlNodeType::Text: Console::WriteLine(
"{0}Text node: Value='{1}'", indent(reader->Depth), reader->Value);
break;
case XmlNodeType::XmlDeclaration: Console::WriteLine(
"Xml Declaration node: Name='{1}'", indent(reader->Depth), reader->Name);
if (reader->HasAttributes)
{
while (reader->MoveToNextAttribute())
{
Console::WriteLine(
"{0}Attribute node: Name='{1}' Value='{2}'", indent(reader->Depth), reader->Name, reader->Value);
}
}
reader->MoveToElement(); Console::WriteLine(
"End Xml Declaration node: Name='{1}'", indent(reader->Depth), reader->Name);
break;
case XmlNodeType::Whitespace: // Ignore white space break;
default:
Console::WriteLine(
"***UKNOWN*** node: Name='{1}' Value='{2}'", indent(reader->Depth), reader->Name, reader->Value);
}
}
}
C H A P T E R 1 3 ■ X M L |
567 |
catch (XmlException ^e)
{
Console::WriteLine("\n\n\nSplitting XML Aborted with error: {0}", e->Message);
}
finally
{
if (reader->ReadState != ReadState::Closed)
{
reader->Close();
}
}
}
The preceding code, though longwinded, is repetitively straightforward and, as pointed out, resembles the processing of a file in many ways.
One neat little trick this example shows is how you can use the XmlReader class’s Depth property to indent your output based on the depth the current node is within the tree. All I do is simply indent an additional three spaces for each depth:
String ^indent(Int32 depth)
{
String ^ind = "";
return ind->PadLeft(depth * 3, ' ');
}
You process all XML within an XmlException try/catch block because every XML method in the
.NET Framework class library can throw an XmlException.
You start by opening the XML file. Then you read the file, and finally you close the file. You place the Close() method in a finally clause to ensure that the file gets closed even on an exception. Before you close the file, you verify that the file had in fact been opened in the first place. It is possible for the Create() method of the XmlReader class to throw an XmlException and never open the XML file:
XmlReader ^reader; try
{
reader = XmlReader::Create("Monsters.xml"); while (reader->Read())
{
//...Process each node.
}
}
catch (XmlException ^e)
{
Console::WriteLine("\n\n\nSplitting XML Aborted with error: {0}", e->Message);
}
finally
{
if (reader->ReadState != ReadState::Closed)
{
reader->Close();
}
}