"Using XmlWriterSettings, XmlWriter, and the Static
Create Methods"
This is the second in a series of three articles that look
in detail at how the new features of the XmlReader and XmlWriter classes in version 2.0 of the
.NET Framework can be used to read and write XML documents, and interact with
the new XML document store objects. The topics covered in the previous article
are:
- The new "settings" classes and static Create methods for XmlReader and XmlWriter
- Creating and using an XmlReader to read and validate XML
documents and fragments
- Some of the useful new features of the XmlReader class
In this article, we move on to look at the XmlWriter and XmlWriterSettings classes, and how they can be used to write XML documents and fragments more
easily and more efficiently than in version 1.x of .NET. The topics we'll
be covering are:
- Using the XmlWriterSettings class to generate XmlWriter instances
- Using an XmlWriter to write XML documents and fragments
- Controlling the output format and encoding of the XML
document
- Streaming XML through an XmlReader and XmlWriter
- Some of the useful new features of the XmlWriter class
As in the previous article, we'll look into the issues
involved in using the new classes, the reasoning behind the changes, and how
the new features simplify your code and provide better overall efficiency for
your applications.
The XmlWriterSettings Class
The previous article discussed the concepts behind the new
"settings" classes for XmlReader and XmlWriter. The two new classes named XmlReaderSettings and XmlWriterSettings can be used to maintain a consistent set of behaviors when generating instances
of readers and writers on demand, without having to repeatedly set their
properties. This has several benefits in that it:
- Reduces the code you have to write
- Allows the framework to make optimizations in the reader
or writer based on the settings, for example omitting validation support
if this is not required
- Provides classes that can execute more efficiently in
circumstances where the extra features are not required
- Allows you to create instances of the abstract base
classes, rather than having to instantiate classes that inherit from XmlReader or XmlWriter
- Allows the XmlReader and XmlWriter to be extended in future
releases without breaking your code, and therefore removes the need for
multiple concrete implementations aimed at different scenarios
The "settings" classes are used
in the new static Create method, of the XmlReader and XmlWriter classes, as demonstrated in the previous article. So, having
seen how you use an XmlReaderSettings instance to specify the behavior of an XmlReader, the way you use the XmlWriterSettings class will be obvious. Of course, XmlWriter has a different set of
properties compared to XmlReader,
and these properties are reflected in the XmlWriterSettings class. Figure 1 shows
the XmlWriterSettings class, and you can see that some of the properties are the similar to XmlReaderSettings.
There is a CheckCharacters property that controls whether illegal XML characters are permitted, and a CloseOutput property that automatically closes the underlying output stream when the writer
is closed.

Figure 1 - The XmlWriterSettings
Class
You can specify if the elements in the output are placed on
a new line and indented, whether each attribute is placed on new line, and the
characters that are used for the indentation and the line breaks. Even more
control over the output format is provided by the NewLineHandling enumeration. By default,
any new line characters between elements and attributes in the document are
replaced by the standard Windows new line characters \r\n (i.e. the default is NewLineHandling.Replace).
However, you can turn off this replacement using the value NewLineHandling.None,
or specify that the original new line characters are preserved (except that new
line characters separating attributes are replaced by a single space) using the
value NewLineHandling.Entitize.
Like XmlReader,
you can specify the conformance level for an XmlWriter so that it will generate output
that is not actually a complete XML document when you only want to write
fragments of XML. For example, as you'll see shortly, you can create XML
fragments that contain multiple root nodes.
If you are using the XmlWriter to stream output from an XSL-T
transformation, using the new XslCompiledTransform class, the read-only OutputMethod property indicates the format of the output that is generated by the writer.
This is a value from the XmlOutputMethod enumeration shown in Figure 1, allowing you to detect whether the output is serialized
as HTML, XML, or just the text content is serialized.
Finally, the XmlWriterSettings class exposes a reference to an Encoding class
instance that specifies the encoding to be applied to the document that is
generated. The default is System.Encoding.UTF8,
but you can change this to suit the requirements of your application. Some of the ways that you can use the XmlWriterSettings class
are discussed next.
Creating an XmlWriter Using the XmlWriterSettings Class
To create an XmlWriter instance, you
first instantiate an instance of the XmlWriterSettings class, set the properties you want, and the call the Create method of the XmlWriter class. For example, this code
creates an XmlWriter that checks for invalid characters in the XML you write, and closes
the underlying stream when the reader is closed. Notice how we use the new Using construct to ensure that the writer is correctly disposed at the
end of the process. This ensures that the XmlWriter is closed,
even if we forget to call the Close method (the Using construct was available in C# in version 1.1, and is now available
in VB.NET in version 2.0):
Dim ws As New XmlWriterSettings()
ws.CheckCharacters = True
ws.CloseOutput = True
Using xw As XmlWriter = XmlWriter.Create(sFilePath,
ws)
Try
' ... create the XML document here ...
xw.Close()
Catch ex As Exception
' ... display error details ...
End Try
End Using
You can, of course, create the XmlWriter without taking advantage of the Using method, for
example if you want to return it from a function so that it can be accessed
from elsewhere in your application. The function shown next creates an XmlWriter using the static Create method, taking as
parameters a reference to an XmlWriterSettings instance and a Stream for the output to be sent to:
Function GetXmlWriter(ws As XmlWriterSettings,
outStream As Stream) As XmlWriter
Dim xw As XmlWriter = Nothing
Try
xw = XmlWriter.Create(outStream, ws)
Return xw
Catch ex As Exception
Try
xw.Close()
Catch
End Try
Return Nothing
End Try
End Function
You could then use this function like this:
Dim settings As New XmlWriterSettings()
settings.CheckCharacters = True
settings.CloseOutput = True
' write an XML document to the ASP.NET
Response
Dim webWriter As XmlWriter = GetXmlWriter(settings,
Response.OutputStream)
If Not (webWriter Is Nothing) Then
' ... create the XML document here ...
webWriter.Close()
End If
...
' write an XML document to the screen
settings.Indent = True
settings.Encoding = Encoding.ASCII
Dim screenWriter As XmlWriter = GetXmlWriter(settings,
Console.OpenStandardOutput())
If Not (screenWriter Is Nothing) Then
' ... create the XML document here ...
screenWriter.Close()
End If
Creating an XML Document with an XmlWriter
Once you've created the XmlWriter, you can use it to generate the output you need. The XmlWriterin version 2.0 supports pretty much the same set of methods for
writing XML elements, attributes, comments, declaration, processing
instructions, etc., as the version 1.xXmlWriter class. So you
can create a simple XML document, once you've created the XmlWriter, like this:
...
xw.WriteStartDocument(True)
xw.WriteComment("Created at "
& DateTime.Now.ToString("hh:mm:ss"))
xw.WriteStartElement("root-node")
xw.WriteElementString("child-node",
"The element value")
xw.WriteStartElement("next-child")
xw.WriteAttributeString("some-attribute",
"A value")
xw.WriteAttributeString("another-attribute",
"Another value")
xw.WriteValue("The next element
value")
xw.WriteEndElement()
xw.WriteEndDocument()
xw.Close()
This creates an XML document containing a
root node and two child nodes, with two attributes on the second child node.
The result is:
<?xml
version="1.0" encoding="utf-8"
standalone="yes"?>
<!--Created
at 13:37:46-->
<root-node>
<child-node>The element value</child-node>
<next-child
some-attribute="A
value"
another-attribute="Another
value">The next element value</next-child>
</root-node>
Note how the WriteStartElement and WriteElementString methods allow you to create the nested hierarchy. The WriteStartElement method creates the opening element tag, and you then populate the
element using the WriteValue, WriteChars or WriteString method. Calling WriteEndElement automatically closes the most recently opened element by writing the
appropriate end tag. The WriteElementString method generates a
complete element, including the opening and closing tags. Similar methods named WriteStartAttribute, WriteEndAttribute and WriteAttributeString allow you to add attributes to the elements as they are created.
The WriteStartDocument method prepares the writer and generates the opening <?xml
version="1.0"?> declaration, while
the WriteEndDocument method automatically closes all open elements in the correct order
and resets the writer. Other methods of the XmlWriter class (and
there are lots of them) include WriteDocType (which
creates a !DOCTYPE declaration), WriteComment, WriteCData (which creates a [!CDATA] section), WriteCharEntity, WriteEntityRef, WriteProcessingInstruction and WriteWhitespace. Look up
"XmlWriter
methods" in the Help file or SDK for a full
list and descriptions of each one.
Writing Namespaces and Qualified Names
Most of the XML documents you generate will
need to include namespace declarations, and the XmlWriter provides
support for this in all of the "write" methods that generate elements
and attributes. These methods accept a String parameter that is
the namespace URI within which the element or attribute will live. If the
document contains a declaration of a namespace prefix for that URI, the methods
automatically use the prefix when generating the element or attribute by
pre-pending it to the local name.
You can see how this works using the
example page we provide, named xmlwritersettings.aspx.
This page, shown in Figure 2, demonstrates many of the features of the XmlWriterSettings and XmlWriter class that we discuss in this article. You can run or download all
of the samples from our Website at http://www.daveandal.net/articles/readwritexml/.

Figure 2 - The XmlWriterSettings
and XmlWriter Example Page
We'll look at the various option settings
in the page shortly, but for the moment concentrate on the XML document that is
generated by the page. This contains a default namespace on the root-node element, as well as the declaration of a namespace prefix "qn" that represents a different namespace. The last element
within the root-node element uses this namespace prefix to place it within in the second
namespace. Generating this kind of multi-namespace XML document is easy using
the XmlReader - you just need to be aware of a few issues that affect the
generated output.
Generating Default Namespace Declarations
The first point to note is when you want to
apply a default namespace using the xmlns attribute without
a namespace prefix declaration. Following the rules of XML, the root node of
your document also lives in this namespace, but the namespace is not declared
when you call the WriteStartElement method to create the opening tag for the root element. Therefore
you must specify this namespace when you call the WriteStartElement method:
xw.WriteStartElement("root-node",
"http://testdemo")
Then you can add the xmlns attribute if you wish, but in fact it's not necessary because the XmlWriter detects that you are now in a new namespace and add the attribute xmlns="http://testdemo" to the element automatically. If you look at the source code for the example
page (there is a [view
source] link at the bottom of the page), you'll see this in
the code comments.
The same process occurs if you call WriteStartElement or WriteElementString and specify a new namespace. The XmlWriter automatically adds an xmlns attribute to the element so that it
is the new default namespace for the enclosed XML content:
xw.WriteElementString("child-node",
"http://testdemo/children", "The element value")
This creates the element child-node as:
<child-node
xmlns="http://testdemo/children">The element
value</child-node>
To "escape" from this namespace, and return to the
previous namespace, you use the WriteFullEndElement method.
Generating Namespace Prefix Declarations and Qualified
Elements
If you look back at Figure 2, you'll see
that the last element in the document contains the prefix "qn":
<qn:qualified-node>Another
element value</qn:qualified-node>
This namespace prefix is declared in the
root-node element as:
<root-node
xmlns:qn="http://testdemo/names" xmlns="http://testdemo">
To generate this namespace declaration, we
first use the technique described above to generate the root-node element with the default namespace xmlns="http://testdemo", then use the overload of the WriteAttributeString method that accepts four parameters to specify the xmlns attribute name, the namespace prefix qn, omit the namespace
for this attribute (so it lives in the default namespace and therefore has no
prefix), and the value for this attribute - the namespace we want to associate
with the new prefix:
xw.WriteStartElement("root-node",
"http://testdemo")
xw.WriteAttributeString("xmlns",
"qn", Nothing, "http://testdemo/names")
Now we can generate elements that use the
new namespace prefix simply by specifying the namespace when we call the WriteElementString or WriteAttributeString method:
xw.WriteElementString("qualified-node",
"http://testdemo/names", "Another element value")
However, the new overload of the WriteElementString method added to the XmlWriter in version 2.0 allows you to generaqte elements in a separate namespace in a
single operation - without having to add the namespace prefix declaration to
the root node if this suits the document format you want. This overload accepts
four String values: the namespace prefix, the element name, the
Namespace URI and the element value - for example:
xw.WriteElementString("mqn",
"qtwo-node", "http://testdemo/morenames", "Some
value")
This generates an element containing both
the namespace prefix and the declaration of that prefix, placing this element
into that namespace without changing the default namespace:
<mqn:qtwo-node
xmlns:mqn="http://testdemo/morenames">Some value</mqn:qtwo-node>
Controlling the Output Format
The example page shown in Figure 2 contains
the results of writing the XML document. In that figure, the document occupies
multiple lines separated by carriage returns and is indented to make it easy to
see what it contains. However, this is not the default format for the output
from an XmlWriter. Unless you like to sit and admire your elegantly formatted XML
documents in a text editor, the fact that they contain non-significant
white-space (such as multiple spaces, tabs and carriage returns) between each
element is irrelevant. In effect, XML is a stream of characters, with the data
delimited by the node tags and a single space between each attribute.
The reason that Figure 1 shows the XML with
carriage returns and indents is because we set these two options using the
checkboxes at the top of the page - making it easy to see what the XML
contains. Without it, the XML is displayed on a single line that scrolls off to
the right of the browser window.
The XmlWriterSettings class
exposes four properties that you can use to specify the formatting of the XML
that is generated by the XmlWriter(s) you create from it. You can
experiment with these in the example page - Figure 3 shows the effects of the
following settings:
' indent the output and insert line breaks
ws.Indent = True
' start each attribute on a new line
ws.NewLineOnAttributes = True
' use a Tab for indents instead of the
default two spaces
ws.IndentChars = ControlChars.Tab
' use two Return characters instead of one
ws.NewLineChars = ControlChars.CrLf & ControlChars.CrLf

Figure 3 - Applying Indenting
and Custom NewLine Characters with an XmlWriterSettings Instance
You can also control whether the <?xml version="1.0"?> declaration is generated - in some cases you may want to omit this. For
example, if you are generating output from multiple writers to create a
compound document, you will only want the declaration to appear once at the
start of the document. The example page contains a checkbox where you can set
the OmitXmlDeclaration property of the XmlReaderSettings instance to True to see this in action.
Writing XML Fragments
As well as generating well-formed XML
documents, you can use the XmlWriter to create
fragments that are not - on their own- well-formed XML. This simply involves
setting the ConformanceLevel property of the XmlWriterSettings instance to ConformanceLevel.Fragment:
ws.ConformanceLevel = ConformanceLevel.Fragment
However, there is an issue to be aware of
in this case. You cannot call the WriteStartDocumentor WriteEndDocument method when you use ConformanceLevel.Fragment. And, as the WriteStartDocument method generates the
XML declaration, this means that you will not get <?xml
version="1.0"?> at the start of
the output (you would not include this anyway if the fragment is not a
well-formed document). The code in our example page checks the value of the ConformanceLevel property, and does not call WriteStartDocument or WriteEndDocument if a fragment is being generated. However, it does add a second element at the
root level of the document to prove that you can create fragments that are not
well-formed documents:
If ws.ConformanceLevel <> ConformanceLevel.Fragment
Then
' cannot call WriteStartDocument for an
XML fragment
xw.WriteStartDocument(True)
End If
... write contents of document here as
before ...
If ws.ConformanceLevel = ConformanceLevel.Fragment
Then
' add a second root node, this is illegal
in a valid document
xw.WriteElementString("another-root-node",
"This is a now a fragment")
Else
' cannot call WriteEndDocument for an XML
fragment
xw.WriteEndDocument()
End If
Figure 4 shows the results. You can see the
fragment with its two root nodes, and there is no XML declaration at the start:

Figure 4 - Creating an XML
Fragment with an XmlWriterSettings and XmlWriter
Specifying the Output Encoding
The final feature that our example page
demonstrates is how you can set the encoding of the XML documents you create
with the XmlWriterSettings and XmlWriter classes. The default encoding for XML document created this way is
UTF-8. The XmlWriter uses an instance of the UTF8Encoding class to
encode the output, so that it is suitable for use in almost all XML parsers,
and in Web Services and other applications.
However, you can use the Encoding property of the XmlWriterSettings class
to specify an alternative encoding if you wish. The drop-down list in the
example page contains six values: ASCII, UTF7, UTF8, UTF32, Unicode (equivalent to UTF-16), and BigEndianUnicode. These correspond to the encoding classes available in the .NET
Framework, and the code in the page applies the one you select (when you also
set the checkbox in the page) using the static properties of the Encoding class:
If chkEncoding.Checked Then
Select Case lstEncoding.SelectedItem.Text
Case "ASCII"
ws.Encoding = Encoding.ASCII
Case "UTF7"
ws.Encoding = Encoding.UTF7
Case "UTF8"
ws.Encoding = Encoding.UTF8
Case "UTF32"
ws.Encoding = Encoding.UTF32
Case "Unicode"
ws.Encoding = Encoding.Unicode
Case "BigEndianUnicode"
ws.Encoding = Encoding.BigEndianUnicode
End Select
End If
If you run the example, and try different
values, you'll see the encoding in the opening XML declaration. Notice that,
because the Web browser automatically translates most encodings into the same
visual output (see Figure 5), you don’t see any other difference except where
you select "UTF7" - which the browser cannot translate!

Figure 5 - Specifying the
Encoding with an XmlWriterSettings and XmlWriter
Streaming XML from an XmlReader to an XmlWriter
The XmlReader is really just
a pull-model parser that exposes the XML as a stream. Meanwhile, the XmlWriter is an object that converts a stream into another format or persists
it to another object. For example, an XmlReader can take its
input from a disk file, a stream or another reader instance; while the XmlWriter can generate its output as a disk file, a stream, a StringBuilder or another writer instance. This means that you can link the XmlReader and XmlWriter together so that, as you read nodes from the XmlReader, you generate the output you require through the XmlWriter.
Why is this useful? Well, one particular
case is when working with very large XML documents that you don’t want to load
into memory using a document store object such as XmlDocument. It's also very efficient, which is useful if - for example - you
just need to modify the occasional value in the XML or perform some kind of
transformation process or business logic. Of course, taking this to its logical
conclusion, the XmlReader and XmlWriter can also be used as the input and output vehicles for other classes
such as XslCompiledTransform, in which case you hand off the actual processing of the XML to
another object instead. In the next section, you'll see an example of streaming
XML, along with some of the other useful new features of the XmlWriter class.