The Abstract Base Classes

only for RuBoard

As we saw in Chapter 5, "MSXML Parser," the MSXML Parser provides two models for working with XML data: push and pull models. A pull model queries a document, and a push model involves notifications from the parser. A pull model typically requires the entire XML document to be loaded, and a push model allows for forward-only reading of a document. In MSXML, the push model is implemented by using the SAX interfaces, and the pull model is implemented by using the DOM interfaces.

The DOM implementation of a pull model loads the entire document into memory so that it can be navigated and updated easily. The problem with loading the entire document into memory is that it is not scalable: Loading a large document in its entirety requires significant memory. Multiply the memory consumption for one large document by potentially tens of thousands of users, and you can see why you want to reduce the memory footprint as much as possible. The DOM, however, is a great choice for working with smaller documents that require updating. The DOM model is retained in .NET through the XmlDocument class.

Because a push model doesn't require the entire document to be loaded, it uses less memory and is better suited for parsing large documents. The downside of the push model is that it is difficult to add and update nodes in the document. Constant maintenance of state information is required to know with what node you are working. In .NET, SAX is not supported. Rather, the designers of the System.Xml classes developed a new forward-only, stream-based model that mixes the document-based approach of the DOM with the forward-only nature of SAX. The classes that form the foundation of this model are the XmlReader and XmlWriter classes. Both classes are abstract, which means that they require an implementation class to create a new instance. Figure 6.1 shows you the available implementation classes.

Figure 6.1. The available implementations of XmlReader and XmlWriter in the .NET Framework.
graphics/06fig01.gif

XmlReader

As its name implies, the XmlReader class enables you to read an XML document. More specifically , the XmlReader provides a forward-only, non-cached mechanism to read XML data. This is most analagous to an ActiveX Data Objects (ADO) firehose cursor.

The XmlReader is an abstract class, so let's look at its implementation classes: XmlTextReader , XmlNodeReader , and XmlValidatingReader .

XmlTextReader

The XmlTextReader class is a concrete implementation of the XmlReader class. It's one of the classes that you are going to frequently work with in your XML and ASP.NET applications.

The DOMDocument class in MSXML provides a loadXML() method that accepts a string representation of the XML document and works with it. The XmlTextReader class in .NET does not provide such a method. Instead, it provides 14 different overloaded New() methods to instantiate an XmlTextReader .

Populating an XmlReader

Let's load an XmlTextReader with data by loading the XML from a string. We display a table that shows each node in the document and some properties of each node. Listing 6.1 shows the code that is used to build the XML document as a string in memory, load the document into an XmlReader , and interrogate the document's nodes.

Listing 6.1 Interrogating the Nodes in an XmlReader
 <%@ Import Namespace="System.Xml"%>  <%@ Import Namespace="System.Text"%>  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  <html>    <head>      <title>WebForm1</title>    </head>  <LINK rel="stylesheet" type="text/css"  href="http://localhost/chapters/SystemXML/Styles.css">    <body MS_POSITIONING="GridLayout">   <script language="vb" runat="server">       Private Sub Page_Load(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles MyBase.Load          Dim sb As StringBuilder          Dim reader As XmlTextReader          sb = New StringBuilder()          With sb              .Append("<DATA xmlns=""urn:foo:bar"">")              .Append("<PERSON name=""Carson Allen Evans""/>")              .Append("<PERSON name=""Deanna Evans"">Hello, Wife</PERSON>")              .Append("</DATA>")          End With  reader = New XmlTextReader(sb.ToString, XmlNodeType.Document, Nothing)  writeTable(reader)      End Sub      Private Sub writeTable(ByVal reader As XmlReader)          With Response              .Write("<TABLE border=""1"">")              .Write("<TR>")              .Write("<TH>Name</TH><TH>NodeType</TH><TH>NodeValue</TH>")  .Write("<TH>Depth</TH><TH>NamespaceURI</TH><TH>BaseURI</TH><TH>LocalName</TH>")              .Write("<TH>IsStartElement</TH><TH>IsEmptyElement</TH>")              .Write("</TR>")              Do While reader.Read()                  .Write("<TR>")                  .Write(getTableData(reader.Name))                  .Write(getTableData(reader.NodeType.ToString))                  .Write(getTableData(reader.Value))                  .Write(getTableData(reader.Depth))                  .Write(getTableData(reader.NamespaceURI))                  .Write(getTableData(reader.BaseURI))                  .Write(getTableData(reader.LocalName))                  .Write(getTableData(reader.IsStartElement))                  .Write(getTableData(reader.IsEmptyElement))                  .Write("</TR>")              Loop              .Write("</TABLE>")          End With      End Sub      Private Function getTableData(ByVal data As String) As String          getTableData = String.Concat("<TD>", data, "</TD>")      End Function   </script>    </body>  </html> 

The output for Listing 6.1 is depicted in Figure 6.2.

Figure 6.2. Each node in the XmlTextReader is interrogated for its properties.
graphics/06fig02.gif

Listing 6.1 shows the use of a StringBuilder object to build an XML string, but could have easily concatenated the strings together to form the input document.

Another method to load an XmlReader without using a file or database involves using a Stream object. Let's use a MemoryStream object to create a reader based on a stream of data held in memory. The updated code is shown in Listing 6.2.

Listing 6.2 Reading XML Data From a MemoryStream
 <%@ Import Namespace="System.Xml"%>  <%@ Import Namespace="System.Text"%>  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  <html>    <head>      <title>WebForm1</title>    </head>  <LINK rel="stylesheet" type="text/css"  href="http://localhost/chapters/SystemXML/Styles.css">    <body MS_POSITIONING="GridLayout">  <script language="vb" runat="server">      Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) graphics/ccc.gif Handles MyBase.Load          Dim sb As StringBuilder          Dim reader As XmlTextReader          Dim memStream As IO.MemoryStream          Dim memStreamWriter As IO.StreamWriter          sb = New StringBuilder()          With sb              .Append("<DATA xmlns=""urn:foo:bar"">")              .Append("<PERSON name=""Carson Allen Evans""/>")              .Append("<PERSON name=""Deanna Evans"">Hello, Wife</PERSON>")              .Append("</DATA>")          End With  memStream = New IO.MemoryStream()   memStreamWriter = New IO.StreamWriter(memStream, System.Text.Encoding.UTF8)   memStreamWriter.Write(sb.ToString)   memStreamWriter.Flush()   memStream.Position = 0  reader = New XmlTextReader(memStream)          writeTable(reader)          On Error Resume Next          memStreamWriter.Close()          memStream.Close()          reader.Close()      End Sub      Private Sub writeTable(ByVal reader As XmlReader)          With Response              .Write("<TABLE border=""1"">")              .Write("<TR>")              .Write("<TH>Name</TH><TH>NodeType</TH><TH>NodeValue</TH>")  .Write("<TH>Depth</TH><TH>NamespaceURI</TH><TH>BaseURI</TH><TH>LocalName</TH>")              .Write("<TH>IsStartElement</TH><TH>IsEmptyElement</TH>")              .Write("</TR>")              Do While reader.Read()                  .Write("<TR>")                  .Write(getTableData(reader.Name))                  .Write(getTableData(reader.NodeType.ToString))                  .Write(getTableData(reader.Value))                  .Write(getTableData(reader.Depth))                  .Write(getTableData(reader.NamespaceURI))                  .Write(getTableData(reader.BaseURI))                  .Write(getTableData(reader.LocalName))                  .Write(getTableData(reader.IsStartElement))                  .Write(getTableData(reader.IsEmptyElement))                  .Write("</TR>")         Loop         .Write("</TABLE>")         End With     End Sub     Private Function getTableData(ByVal data As String) As String         getTableData = String.Concat("<TD>", data, "</TD>")     End Function   </script>    </body>  </html> 

The output for Listing 6.2 is identical to what's depicted in Figure 6.2.

In the highlighted code in Listing 6.2, we created a MemoryStream object and used a StreamWriter object to write data to the stream. We called the Flush() method of the StreamWriter class to commit the changes to the underlying stream and then reset the stream's internal pointer to the beginning of the stream by using the Position() property. Resetting the stream's internal pointer to the beginning of the stream is important. Otherwise , no data can be read from the stream. The last line of highlighted text shows that we created the XmlTextReader class by using the MemoryStream object and then called our writeXML function by using XmlReader .

Retrieving External Data

The XmlTextReader accepts a URL as a parameter for one of its overloaded New() functions:

 reader = New  XmlTextReader("http://www.xmlandasp.net/examples/xmlfile1.xml") 

This allows you to access resources over the Internet or within your intranet. Several of the overloaded versions of the New() function accepts a URL for resolving external resources. The XmlTextReader accepts a XmlResolver property for resolving these external resources. This property accepts an object of type, you guessed it, XmlResolver . By specifying the URL for the external resource in the URL parameter of the overloaded function and providing an XmlResolver , you can work with external resources and resolve only those resources that you want to retrieve remotely.

For example, suppose that you are working with an XML Schema file that's located on a remote server. You want to retrieve the entire XML Schema to ensure that it is up to date. To do this, use this code:

 Private Sub getdata()          Dim resolver As XmlUrlResolver = New XmlUrlResolver()          Dim baseURI As Uri = New Uri("http://www.sample.com/sample.xsd")          Dim relativeURI As Uri = resolver.ResolveUri(baseURI, "Customer.xsd")          Dim inputStream As IO.Stream = resolver.GetEntity(relativeURI, Nothing, graphics/ccc.gif GetType(IO.Stream))          Dim reader As XmlTextReader = New XmlTextReader(inputStream)          Do While reader.Read              Response.Write(reader.ReadOuterXml())          Loop          On Error Resume Next          inputStream.Close()          reader.Close()      End Sub 

But what if those external resources are secured and require authentication that's different than the currently logged-in user? Traditional means for solving this problem included developing a service to run as a specific user for the request, or calling Win32 APIs to log into a resource with specific credentials.

The designers of the XmlReader class provided the Credentials property of the XmlResolver class. Using the CredentialCache and NetworkCredential classes found in the System.Net namespace, you can specify authentication to external resources, as shown here:

 Private Sub getdata()  Dim credentials As Net.CredentialCache = New Net.CredentialCache()   credentials.Add(New Uri("http://www.xmlandasp.net/"), "Basic", New graphics/ccc.gif Net.NetworkCredential("kirke", "mypass"))   credentials.Add(New Uri("http://www.xmlandasp.net/schemas/auth"), "Digest", New graphics/ccc.gif Net.NetworkCredential("kirke", "mypass"))  Dim resolver As XmlUrlResolver = New XmlUrlResolver()      Dim baseURI As Uri = New Uri("http://www.xmlandasp.net/schemas/auth")  resolver.Credentials = credentials  Dim relativeURI As Uri = resolver.ResolveUri(baseURI, "Customer.xsd")      Dim inputStream As IO.Stream = resolver.GetEntity(relativeURI, Nothing, graphics/ccc.gif GetType(IO.Stream))      Dim reader As XmlTextReader = New XmlTextReader(inputStream)      Do While reader.Read          Response.Write(reader.ReadOuterXml())      Loop      On Error Resume Next      inputStream.Close()      reader.Close()  End Sub 
Accessing Data in the XmlReader

Listings 6.1 and 6.2 both used a custom function called writeTable that interrogates an XMLReader implementation object. You can load the reader prior to calling the function and then pass the populated reader to the writeTable function to display its results. Each call to the Read() method within the writeTable function advances the pointer and returns if the method call was successful. If the call was successful, another node must be processed . Otherwise, you're at the end of the XML document. This can be counter-intuitive to developers who are used to checking for an End of File (EOF) marker that signals the end of processing. When the Read() method returns a Boolean false, this is the equivalent of an EOF condition.

Using a Do..While loop and accessing the reader's properties inside the loop is the typical design pattern for working with the various stream-based readers in .NET, such as the TextReader object and StreamReader object.

Referring to Figure 6.1, you can see that the elements and text nodes are displayed in the output, but the attributes are not. This is an important point to remember when working with the XmlTextReader : The attributes are not output unless the code retrieves them. Otherwise, they are skipped . This ability to skip nodes unless otherwise requested adds to the efficiency of the XmlReader implementation.

To iterate the attributes for the current node, you can use the MoveToFirstAttribute method and the MoveToNextAttribute method. If you know the name or index of an attribute, you can also use the GetAttribute method to return the specific attribute.

As of this writing, a bug exists when you call MoveToNextAttribute and then pass the XmlTextReader reference to another function. The bug causes the pointer to be set back on the first attribute. For example, the following code causes an endless loop because the pointer is always positioned on the first attribute and never advances:

 While (reader.MoveToNextAttribute())                          Diagnostics.Debug.WriteLine(reader.Name & " " &reader.Value)                          writeData(reader)                      End While 

One way around this is to use the AttributeCount property of the XmlTextReader to use its indexer. The internal attribute collection is 0-based, so you need to offset the indexer by subtracting the count to get the correct upper index (see Listing 6.3).

Listing 6.3 Iterating the Attributes of an XmlReader
 <%@ Import Namespace="System.Xml"%>  <%@ Import Namespace="System.Text"%>  <%@ Import Namespace="System.Xml"%>  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  <html>    <head>      <title>WebForm1</title>    </head>  <LINK rel="stylesheet" type="text/css"  href="http://localhost/chapters/SystemXML/Styles.css">    <body MS_POSITIONING="GridLayout">   <script language="vb" runat="server">      Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) graphics/ccc.gif Handles MyBase.Load          Dim sb As StringBuilder          Dim reader As XmlTextReader          Dim memStream As IO.MemoryStream          Dim memStreamWriter As IO.StreamWriter          sb = New StringBuilder()          With sb              .Append("<DATA xmlns=""urn:foo:bar"">")              .Append("<PERSON name=""Carson Allen Evans""/>")              .Append("<PERSON name=""Deanna Evans"">Hello, Wife</PERSON>")              .Append("</DATA>")          End With          memStream = New IO.MemoryStream()          memStreamWriter = New IO.StreamWriter(memStream, System.Text.Encoding.UTF8)          memStreamWriter.Write(sb.ToString)          memStreamWriter.Flush()          memStream.Position = 0          reader = New XmlTextReader(memStream)          writeTable(reader)          On Error Resume Next          memStreamWriter.Close()          memStream.Close()          reader.Close()      End Sub      Private Sub writeTable(ByVal reader As XmlTextReader)          With Response              .Write("<TABLE border=""1"">")              .Write("<TR>")              .Write("<TH>NodeType</TH><TH>Name</TH><TH>NodeValue</TH>")  .Write("<TH>Depth</TH><TH>NamespaceURI</TH><TH>BaseURI</TH><TH>LocalName</TH>")  .Write("<TH>IsStartElement</TH><TH>IsEmptyElement</TH><TH>XmlSpace</TH>")              .Write("</TR>")               Do While reader.Read()                  getTableRow(reader)                  If reader.HasAttributes Then                      Dim loopVar As Integer                      For loopVar = 0 To reader.AttributeCount - 1                          reader.MoveToAttribute(loopVar)                          getTableRow(reader)                      Next                      reader.MoveToElement()                  End If              Loop              .Write("</TABLE>")           End With      End Sub       Private Function getTableRow(byval reader as XmlReader) as string            With Response              .Write("<TR>")              .Write(getTableData(reader.Name))              .Write(getTableData(reader.NodeType.ToString))              .Write(getTableData(reader.Value))              .Write(getTableData(reader.Depth))              .Write(getTableData(reader.NamespaceURI))              .Write(getTableData(reader.BaseURI))              .Write(getTableData(reader.LocalName))              .Write(getTableData(reader.IsStartElement))              .Write(getTableData(reader.IsEmptyElement))              .Write("</TR>")           end with       End Function       Private Function getTableData(ByVal data As String) As String          getTableData = String.Concat("<TD>", data, "</TD>")      End Function   </script>    </body>  </html> 

The output of Listing 6.3 is shown in Figure 6.3. Notice that this listing is similar to what's shown in Figure 6.2, but adds the document's attributes and their properties to the table.

Figure 6.3. Displaying the attributes of an XML document using XmlReader .
graphics/06fig03.gif

Suppose that you didn't want to grab all the attributes, but only wanted a single attribute. The MoveToElement method enables you to move back to the element to which the current attribute node belongs.

 reader.MoveToAttribute("CustomerID")  writeData(reader)  reader.MoveToElement()  writeData(reader) 

Table 6.1 details the properties of the XmlTextReader class and Table 6.2 details its methods.

Table 6.1. Properties of the XmlTextReader Class

Property Name

Description

AttributeCount

The integer count of attribute nodes for the current node.

BaseURI

Returns the URI location from where the node was loaded as a string. This will be blank if the node was loaded from a stream or a string.

CanResolveEntity

Gets a value indicating if this reader can parse and resolve entities.

Depth

The depth of the current node in the XML document.

Encoding

The encoding of the document.

EOF

Indicates whether the reader is positioned at the end of the stream.

HasAttributes

Boolean that indicates whether the current node has any attributes.

HasValue

Indicates if the current node can have a value. Nodes with NodeType of EndElement cannot have a value.

IsDefault

Boolean that tells if the current node is an attribute that was defined as a default in a schema or DTD.

IsEmptyElement

Returns a Boolean that specifies if the current element is empty.

Item

Gets the value of the attribute at the specified index.

LineNumber

The current line number.

LinePosition

The current line position.

LocalName

The local name of the current node. The localname is the element or attribute name without a namespace prefix.

Name

The qualified name of the current node, including the name of the element or attribute and any associated namespace prefix. If the node is in the default namespace, it returns the local name.

Namespaces

Gets or sets a Boolean that indicates whether its namespaces are supported. The default is true.

NamespaceURI

The namespace URI of the current node. Refer to Figure 6.2 for an example.

NameTable

Gets the XmlNameTable associated with the XmlReader .

NodeType

The type of the current node. Corresponds to the XmlNodeType enumeration. Using the ToString method on the enumeration returns the string representation instead of the numeric value.

Normalization

Gets or sets a value that indicates whether to normalize white space and attribute values.

Prefix

Gets the namespace prefix for the current node. If the node is in the default namespace, it returns an empty string.

QuoteChar

Gets the quotation character that's used to enclose an attribute value.Valid values are single quote ( ' ) or double quote ( " ).

ReadState

One of the ReadState enumeration values that indicate the state of the reader.

Value

The text value of the current node.

WhitespaceHandling

One of the WhitespaceHandling enumeration values that indicate how whitespace should be handled.

XmlLang

Gets the current xml:lang scope.

XmlResolver

Gets or sets the XmlResolver that's used to resolve external resources, such as entities, DTDs, or schemas.

XmlSpace

One of the XmlSpace enumeration values that indicate the current xml:space scope.

Table 6.2. Methods of the XmlTextReader Class

Method Name

Description

Close

Changes the ReadState to ReadState.Closed and releases any resources held. If the reader is based on a stream, it also closes the underlying stream.

GetAttribute

Gets an attribute for the specified index or name.

GetRemainder

Returns a TextReader that contains the remainder of the buffered XML and sets EOF to True.

GetType

Gets the System.Type of the current instance.

IsName

Returns a Boolean that indicates if the string argument is a valid XML name. See www.w3.org/TR/2000/REC-xml-20001006#NT-Name. (Inherited from XmlReader .)

IsNameToken

Returns a Boolean that indicates if the string argument is a valid XML name token. See www.w3.org/TR/2000/REC-xml-20001006#NT-NmToken.

IsStartElement

Calls MoveToContent and tests if the current content node is a start tag or an empty element tag.

LookupNamespace

Resolves a namespace prefix for the current element's scope.

MoveToAttribute

Moves to the attribute with the specified index or name.

MoveToContent

Checks whether the current node is a content node. Content nodes are non-whitespace Text, CDATA , Element, EndElement , EntityReference , or EndEntity nodes. If the current node isn't a content node, the method skips ahead to the next content node or to the end of the file (EOF).

The node types that are skipped include ProcessingInstrucution , DocumentType , Comment , Whitespace , SignificantWhitespace .

MoveToElement

Moves to the element that contains the current attribute node. Use after GetAttribute , MoveToAttribute , MoveToFirstAttribute , or MoveToNextAttribute .

MoveToFirstAttribute

Moves to the first attribute node. Returns true if an attribute exists and positions the cursor on the attribute node; otherwise, returns false and doesn't move the cursor.

MoveToNextAttribute

Moves to the next attribute node. Returns true if an attribute exists and positions the cursor on the attribute node; otherwise, it returns false and doesn't move the cursor.

Read

Reads the next node from the stream. Returns true if the next node was read successfully; otherwise, it returns false.

ReadAttributeValue

Parses the attribute value into one or more Text , EntityReference , or EndEntity nodes. Returns true if there are nodes to return, false if the reader is not positioned on an attribute node or if all attributes have been read. An empty attribute, such as CustomerID="" , returns true with a single node having the value String.Empty .

ReadBase64

Decodes Base64 and returns the decoded binary bytes.

ReadBinHex

Decodes BinHex and returns the decoded binary bytes.

ReadChars

Reads the textual contents of an element into a character buffer. Designed to read large streams of embedded text by calling it successively.

ReadElementString

Reads a text-only element.

ReadEndElement

Checks that the current content node is an end tag and advances the reader to the next node.

ReadInnerXml

Reads all the content, including markup, as a string.

ReadOuterXml

Reads the content, including markup, representing this node and all its children.

ReadStartElement

Checks that the current node is an element and advances the reader to the next node.

ReadString

Reads the content of an element or a text node as a string.

ResetState

Resets the state of the reader to ReadState.Initial .

ResolveEntity

Resolves the entity reference for EntityReference nodes.

Skip

Skips the children of the current node.

XmlNodeReader

The XmlNodeReader class is another concrete implementation class based on the XmlReader abstract class. This class reads the given node in a forward-only, noncached manner. Because it inherits from XmlReader , the members of the class are the same as the XmlReader class. The following code snippet uses an XmlNodeReader to read a DOM document in almost the same way as the other reader implementations:

 Public Sub getNodeReader()          Dim document As XmlNode = New XmlDocument()          document.InnerXml = "<TEST><DATA>testing</DATA></TEST>"          Dim nr As XmlNodeReader = New XmlNodeReader(document)          While nr.Read              Response.Write(nr.Name + "<br>")          End While      End Sub 

You can also mix implementations of the XmlTextReader and XmlNodeReader . For example, you can return an XmlNode back from the XmlTextReader and read it by using the XmlNodeReader . You can also mix implementations of the XmlNodeReade r and XmlDocument or XmlDataDocument , working with an inmemory DOM structure as a stream. This provides efficiency because only one entire copy of the DOM is in memory.

One way that this might be used is with an XmlDocument that's held in the Cache object, as shown here:

 Dim dom As XmlDocument = New XmlDocument()            dom.Load(Server.MapPath("XmlFile1.xml"))            Cache.Insert("XMLDataFile", dom, New  CacheDependency(Server.MapPath("XmlFile1.xml"))) 

Because the entire document is already stored in memory, you don't want to copy its contents and create a new in-memory copy of the document just to read its contents. Instead, you can use the XmlNodeReader object to read its contents as a stream, as shown here:

 Dim nodeReader As XmlNodeReader = New  XmlNodeReader(Cache.Get("XMLDataFile"))          While nodeReader.Read()              response.Write ("<h1>" & nodereader.Name & "</h1>")          End While 
XmlValidatingReader

This section discusses programmatically validating XML documents against XML Schemas by using the XmlValidatingReader class. You need to understand three main components when using the XmlValidatingReader : the actual reader, the XmlSchemaCollection object, and the ValidationEventHandler .

XmlValidatingReader is inherited from XmlReader just as the XmlTextReader class is, Therefore, they share most of the same properties and methods. XmlValidatingReader adds several methods to the mix. These methods and properties are described in Tables 6.3, 6.4, and 6.5.

Table 6.3. Properties Added by XmlValidatingReader

Property Name

Description

Encoding

Gets the encoding attribute for the entire document.

EntityHandling

Specifies if the reader handles entities by expanding general or character entity references.

Namespaces

Indicates if the reader supports namespaces.

Reader

Gets the XmlReader that's used to construct the XmlValidatingReader instance.

Schemas

Returns the XmlSchemaCollection object to use with the XmlValidatingReader .

SchemaType

Gets a SchemaType object for the current node.

ValidationType

Gets or sets the validation type, specified in the ValidationType enumeration (XDR, DTD, XSD, or None) and must be set prior to the first call to the Read() method.

XmlResolver

Sets the XmlResolver object that's used to resolve external resources and XML Schema import and include elements.

Table 6.4. Methods Added by XmlValidatingReader

Method Name

Description

ReadTypedValue

Gets a System.Xml.Schema.XmlSchemaType object for the current node.

Table 6.5. Events Added by XmlValidatingReader

Event Name

Description

ValidationEventHandler

Sets the error handler for receiving information from validation errors. Typically, this event contains code to set a module-level Boolean variable to false.

The ValidationType enumeration is fairly straight forward: Use DTD, XDR, or schema when you're validating against a DTD, XDR schema, or XSD Schema, respectively. If None is specified, no validation is performed and any validation errors that occur are not thrown. The Auto member implicitly checks what type of validation to perform. This is the default validation type if no type is specified. However, some specific behaviors for each validation type exist. Refer to the .NET SDK documentation, "Validation Types of the XmlValidatingReader " for a detailed explanation of each type.

The XmlValidatingReader does precisely what its name implies: It reads XML and validates that XML against an XML Schema. The XmlValidatingReader reads from a stream so it does not actually hold the contents of the stream. It uses an instance of the XmlTextReader to read from the stream, and extends the functionality of the XmlTextReader by validating the node against the schema(s) specified. To associate the XmlTextReader instance with the XmlValidatingReader instance, use the constructor that accepts a text reader:

 //Instantiate a reader to read the XML file  XmlTextReader textReader = new XmlTextReader(xmlFile);  //Associate the ValidatingReader with the XMLTextReader object  XmlValidatingReader validator = new XmlValidatingReader(textReader); 
Associating a Schema with the XmlValidatingReader

After the XmlTextReader is associated with the XmlValidatingReader , you need to supply the schema(s) to the XmlValidatingReader instance. The XmlValidatingReader class exposes a Schemas collection that directs the validator to the schemas that are going to be used. The Schemas collection exposes an Add method that accepts four overloads to locate the schema(s) to be validated against. The following first overload accepts an XmlSchemaCollection object, which is useful if you are working with multiple schemas:

 XmlSchemaCollection schemas = new XmlSchemaCollection();  schemas.Add ("urn:schemas-/xmlandasp-net:framework", new  XmlTextReader(xsdFile));  validatingReader.Schemas.Add(schemas); 

The second overload accepts a single XmlSchema object. As you saw earlier, using this object requires that you either build the schema on the fly or retrieve the schema from an XmlSchemaCollection object. The third overload accepts a namespace and an XmlReader abstract class. For this overload, you could specify an XmlTextReader because XmlTextReader is a concrete implementation of the XmlReader abstract class. This is nearly identical to the previous usage of the XmlSchemaCollection object, but without a separate object reference:

 validatingReader.Schemas.Add("urn:schemas-/xmlandasp-net:customer", new  XmlTextReader(xsdFile)); 

The last overload accepts a namespace for the schema and the URL to the physical file. This last overload is used for the example, but the third overload could've been used just as easily:

 validatingReader.Schemas.Add("urn:schemas-/xmlandasp net:customer","http://www.xmlandasp.net/schemas/fr.xsd"); 
Creating a ValidationEventHandler

As the XmlValidatingReader reads through the stream using the supplied XmlTextReader , it validates the content in the stream to the schema specified and raises an event if the node violates the schema. This event is specified through its ValidationEvent property. Because the event is fired when the schema is invalid, you need a class member variable to determine through the function containing ValidatingReader if the schema was valid.

To wire up the event handler and the XmlValidatingReader , begin by declaring a function that accepts two parameters: an Object type and a ValidationEventArgs type:

 private void ValidationCallback ( object sender, ValidationEventArgs args )  { System.Diagnostics.Debug.WriteLine ("Validation error: {0}" + args.Message);  isValid = false;  } 

Inside this function is a simple debug output message that lets you know that a validation event occurred and what the message was. You also set the class member variable to false to indicate that the validation was not successful.

The next step is to create a ValidationEventHandler class that uses the previously created function as a callback. C# uses a delegate to wire up the event with the object, as shown here:

 // Wire up the callback to our ValidationCallBack routine  ValidationEventHandler eventHandler = new ValidationEventHandler  (ValidationCallback);  //Wire up the event handler to the validating reader's event handler  validator.ValidationEventHandler += eventHandler; 

In Visual Basic .NET, the same can be accomplished by using the AddHandler method:

 'Wire up the callback to our ValidationCallBack routine  AddHandler validator.ValidationEventHandler, AddressOf ValidationCallback 

The XmlValidatingReader is now associated with the callback function and is fired when the Read process begins.

Validating Using the Read Method

Until this point, all the plumbing has been set in place, but you still haven't validated anything. The actual validation occurs when the Read method is called on each element within the XML instance document, as shown here:

 while (validator.Read())  { //Here, you have access to each element.  We do nothing with this  //because we only want to know if the entire document is valid or not.  } 

The Read method simply reads the XML instance document and validates the element against the schema. If invalid or missing data exists, the event handler is called.

Because the code differs significantly in Visual Basic and C#, both language implementations are shown. Listing 6.4 shows the full example in C#.

Listing 6.4 Creating a Utility Class of XML Functions in C#
 Public Class XMLFunctions  { private Boolean isValid = false;  //Private flag to hold validation results from callback  public Boolean IsXMLValid(string xsdFile, string xmlFile)  { //Instantiate a reader to read the XML file  XmlTextReader textReader = new XmlTextReader(xmlFile);  //Associate the ValidatingReader with the XMLTextReader object  XmlValidatingReader validator = new XmlValidatingReader(textReader);  validator.ValidationType = ValidationType.Auto;  //Create the schema collection  XmlSchemaCollection schemas = new XmlSchemaCollection();  schemas.Add (null, new XmlTextReader(xsdFile));  //Add the list of schemas to validate against to the schemas collection  validator.Schemas.Add(schemas);  // Wire up the callback to our ValidationCallBack routine  ValidationEventHandler eventHandler = new ValidationEventHandler  (ValidationCallback);  //Wire up the event handler to the validating reader's event handler  validator.ValidationEventHandler += eventHandler;  while (validator.Read())  { //Here, you have access to each element.  We do nothing with this  //because we only want to know if the entire document is valid or not.  }  if (isValid)  System.Diagnostics.Debug.WriteLine ("Document is valid.");  else  System.Diagnostics.Debug.WriteLine ("Document is NOT valid.");  validator.Close();  textReader.Close();  Return isValid;  }  private void ValidationCallback ( object sender, ValidationEventArgs args )  { System.Diagnostics.Debug.WriteLine ("Validation error: {0}" + args.Message);  isValid = false;  }  } 

Listing 6.5 shows the same code in Visual Basic .NET.

Listing 6.5 Creating a Utility Class of XML Functions in Visual Basic.NET
 Imports System.Xml  Imports System.Xml.Schema  Public Class XMLFunctions      Private isValid As Boolean = False  'Private flag to hold validation results from graphics/ccc.gif callback      Public Function IsXMLValid(ByVal xsdFile As String, ByVal xmlFile As String) As graphics/ccc.gif Boolean          'Instantiate a reader to read the XML file          isValid = True          Dim textReader As XmlTextReader = New XmlTextReader(xmlFile)          'Associate the ValidatingReader with the XMLTextReader object          Dim validator As XmlValidatingReader = New XmlValidatingReader(textReader)          validator.ValidationType = ValidationType.Auto          'Create the schema collection          Dim schemas As XmlSchemaCollection = New XmlSchemaCollection()          schemas.Add("urn:schemas-vbdna-net:framework", New XmlTextReader(xsdFile))          'Add the list of schemas to validate against to the schemas collection          validator.Schemas.Add(schemas)          'Wire up the callback to our ValidationCallBack routine          AddHandler validator.ValidationEventHandler, AddressOf ValidationCallback          While (validator.Read())              'Here, you have access to each element.  We do nothing with this              'because we only want to know if the entire document is valid or not.          End While          If (isValid) Then              System.Diagnostics.Debug.WriteLine("Document is valid.")          Else              System.Diagnostics.Debug.WriteLine("Document is NOT valid.")          End If          validator.Close()          textReader.Close()          Return isValid      End Function      Private Sub ValidationCallback(ByVal sender As Object, ByVal args As graphics/ccc.gif ValidationEventArgs)          System.Diagnostics.Debug.WriteLine ("Validation error: {0}" + args.Message);          isValid = False      End Sub  End Class 

Take a look at how you might use this sample class. An XML document, sample.xml , and an XML Schema document, validsample.xsd , are used to perform validation by using the sample class. First, list the schema document that's used for validation:

 <?xml version="1.0" encoding="utf-8" ?>  <xsd:schema id="xmlfunctions"                    targetNamespace="urn:schemas-/xmlandasp-net:framework"                    elementFormDefault="qualified"                    xmlns="urn:schemas-/xmlandasp-net:framework"                    xmlns:tns="urn:schemas-/xmlandasp-net:framework"                    xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:element name="root">            <xsd:complexType>                 <xsd:sequence>                      <xsd:element name="child1"/>                      <xsd:element name="child2"/>                      <xsd:element name="child3"/>                 </xsd:sequence>            </xsd:complexType>       </xsd:element>  </xsd:schema> 

Now that you have seen the schema document, it's time to look at the .aspx page that contains the code that's used to call the function. Listing 6.6 shows the .aspx page in its entirety.

Listing 6.6 A Sample .aspx Page that Calls the XMLFunctions Class
 <%@Import Namespace="SystemXml"%>  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  <HTML>       <HEAD>            <title>Listing6_4</title>            <script language="vb" runat="server">           Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As graphics/ccc.gif System.EventArgs)                Dim xmlLib As XMLFunctions = New XMLFunctions()                Dim retValid As Boolean = False                retValid = xmlLib.IsXMLValid(Server.MapPath("validsample.xsd"),                           Server.MapPath(TextBox1.Text))                 Label2.Text = "xmlLib.IsValid = " + retValid.ToString()            End Sub            </script>       </HEAD>       <body>            <form runat="server">                 <P>                      <asp:Label id="Label1" runat="server" Width="115px" Height="16px">XML graphics/ccc.gif File:</asp:Label>                      <asp:TextBox id="TextBox1" runat="server" Width="196px"></ graphics/ccc.gif asp:TextBox></P>                 <P>                      <asp:Label id="Label2" runat="server" Width="224px" Height="21px"/></ graphics/ccc.gif P>                 <P>                      <asp:Button id="Button1" runat="server" OnClick="Button1_Click" graphics/ccc.gif Width="106px" Height="29px"  Text="Button"></asp:Button></P>            </form>       </body>  </HTML> 

When you run the sample, you're prompted for an XML document name. If you enter a valid document, you should see the label's text as xmlLib.IsValid = True . For example, the following XML document is schema-valid:

 <?xml version="1.0" encoding="utf-8" ?>  <root xmlns="urn:schemas-/xmlandasp-net:framework">       <child1/>       <child2/>       <child3/>  </root> 

The following code, however, is not schema-valid and produces a different result. Save the following as badxml.xml in the same directory as the code in Listing 6.6:

 <?xml version="1.0" encoding="utf-8" ?>  <root xmlns="urn:schemas-/xmlandasp-net:framework">       <badnodename/>  </root> 

Figure 6.4 shows how the page looks when it's run. You can see that the class correctly returns the fact that the XML document is not schema-valid.

Figure 6.4. A sample using a utility class for validation.
graphics/06fig04.gif

Note the lack of error handling in this example. Both the C# and Visual Basic .NET versions make the assumption that both the XML and XSD files already exist. You might want to extend this example to include a try..catch block to trap errors that might occur when loading the XML or XSD files fails.

XmlWriter

The XmlWriter abstract class has only one concrete implementation class: XmlTextWriter . Using this class is straight forward. It simply writes out each node in a forward-only, non-cached manner. This is a useful class for creating new XML documents.

Rather than detail each method and property of this class, the following simple example suffices. Listing 6.7 shows an example of using the XmlTextWriter class to create an XML document.

Listing 6.7 Creating an XML Document by Using XmlWriter
 <%@ Import Namespace="System.Xml"%>  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  <HTML>       <HEAD>            <title>Listing6_7</title>             <script language="vb" runat="server">      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)          CreateXmlDocument("c:\myxmldoc.xml")      End Sub      Public Sub CreateXmlDocument(ByVal fileToCreate As String)          Dim writer As XmlTextWriter = New XmlTextWriter(fileToCreate, graphics/ccc.gif System.Text.Encoding.UTF8)          With writer              .Formatting = Formatting.Indented              .WriteStartDocument()              .WriteStartElement("", "COLLEGES", "http://tempuri.org/XMLFile1.xsd")              .WriteStartElement("COLLEGE")              .WriteElementString("NAME", "University of Georgia")              .WriteElementString("MASCOT", "Bulldog")              .WriteEndElement()              .WriteEndElement()              .WriteEndDocument()              .Flush()              .Close()          End With      End Sub            </script>       </HEAD>       <body>            <form id="Form1" method="post" runat="server">                 <asp:TextBox id="TextBox1" runat="server"></asp:TextBox>                 <asp:Button OnClick="Button1_Click" id="Button1"  runat="server" Text="Button"></asp:Button>            </form>       </body>  </HTML> 

It's specified that the XmlTextWriter should create an XML document that is indented by using the Formatting property. Then the actual document is created by using the WriteStartDocument method. This outputs the XML declaration and encoding. The next method call, WriteStartElement , writes an element with no namespace prefix but belongs to the default namespace. The next method call is another call to WriteStartElement . This nests the elements properly. The output of this function creates an XML file with the following data:

 <?xml version="1.0" encoding="utf-8"?>  <COLLEGES xmlns="http://tempuri.org/XMLFile1.xsd">    <COLLEGE>      <NAME>University of Georgia</NAME>      <MASCOT>Bulldog</MASCOT>    </COLLEGE>  </COLLEGES> 

XmlNode

Remember that, in XML, everything is a node. A processing instruction in an XML document is a node, just as an element or an attribute is. Even text contained in an element is referred to as a text node. While working with these various components of XML, it is helpful that they all share a common set of attributes that forms the basis for all the components of an XML document. That basis is realized through the XmlNode class.

Figure 6.5 illustrates that everything in XML is a node, even the document itself.

Figure 6.5. Everything in XML is considered a node.
graphics/06fig05.gif

Before the implementation of XmlNode is defined within the .NET Framework, remember what an XML document is. At first glance, an XML document is nothing more than tags and text. Consider this example:

 <?xml version="1.0" encoding="utf-8" ?>  <customers>       <customer id="kaevans">            <name>Kirk Allen Evans</name>            <topics>                 <topic>Georgia Bulldogs</topic>                 <topic>Atlanta Thrashers</topic>            </topics>       </customer>  </customers> 

This document can be represented visually, as shown in Figure 6.6.

Figure 6.6. An XML document can be represented as a set of nodes.
graphics/06fig06.gif

To make this type of structure navigable, everything contained in the structure must share a common interface or be inherited from the same base class. The base class that all the components of an XML document in .NET inherit from is XmlNode .

This is similar to working with the MSXML parser, where all components share the IXMLDomNode interface to make the document navigable.

To demonstrate how Figure 6.6 arose, look at the following code snippet. We simply move through all the nodes and report what node type was found:

 Public Sub displayTree(ByVal fileToOpen As String)      Dim doc As XmlDocument = New XmlDocument()      Dim child As XmlNode      doc.Load(fileToOpen)      For Each child In doc.ChildNodes          traverseTree(child, 0)      Next  End Sub  Private Sub traverseTree(ByVal node As XmlNode, ByVal indent As Integer)      Dim tempNode As XmlNode      Dim indentChars As String = New String(" ", indent)      Debug.WriteLine(String.Concat(indentChars, node.Name, vbTab, node.NodeType))      If Not node.Attributes Is Nothing Then          If node.Attributes.Count > 0 Then              For Each tempNode In node.Attributes                  '*****RECURSIVE CALL                  traverseTree(tempNode, indent + 4)              Next          End If      End If      If node.HasChildNodes Then          For Each tempNode In node.ChildNodes              '*****RECURSIVE CALL              traverseTree(tempNode, indent + 4)          Next      End If  End Sub 

You can see that each node supports the XmlNode base class when using methods, such as SelectSingleNode , that return an XmlNode object, or SelectNodes that returns a collection of XmlNode objects. You see this in more detail in the section, "Document Navigation."

only for RuBoard


XML and ASP. NET
XML and ASP.NET
ISBN: B000H2MXOM
EAN: N/A
Year: 2005
Pages: 184

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net