Introduction
This article will show how to fork XML processing when using XmlReader
s and XmlWriter
s in .NET Framework 1.1.
Background
Recently, in an application, I had the need to log all XML messages exchanged with service providers.
Being services implemented over the POX/REST (Plain Old XML/REpresentational State Transfer) protocol, I had the disadvantage of not being able to take advantage of the SOAP implementation in the .NET framework but, because I had to do it myself, I would have some control over the messages.
Part of the message content belonged to the protocol implemented by the service provider (something like SOAP), and only a small part of it was service specific content. The service clients use DTOs (Data Transfer Objects) which were serialized using XmlSerializer
s.
To make it more difficult to tamper with message content and to gain some performance, I opted by not using XmlDocument
s, instead, I used XmlReader
s and XmlWriter
s.
Because XmlSerializer
s were being used, the need to log message content posed an interesting problem, because XmlReader
s and XmlWriter
s were, some times, out of my control and under the control of XmlSerializer
s. Besides that, message logging could be optional, therefore, I needed something that I could turn "on" or "off" whenever I needed.
Forking XML processing
The requirement looked simple; I only needed to pass a XmlWriter
that would log every node being processed by the XmlReader
s and XmlWriter
s used to process messages. And all this had to be transparent to the rest of the application (and any library or framework used by the application).
In the presence of such a task, I didn't refrain myself from getting help from the experts. Larry Serflaten pointed out that the concept I was looking for was message forking and Oleg Tkachenko showed me the way to its implementation.
Forking XML reading
To fork the reading operation of XML messages, I opted on building a XmlReader
derived class (XmlForkedReader
) that would receive, on instantiation of its objects, an instance of a XmlReader
derived class used to read the message and an instance of a XmlWriter
derived class used to log the message.
I opted also to implement IDisposable
to take advantage of the use of using
(Using
in Visual Basic) blocks and to be compliant with the implementation of the XmlReader
in .NET framework 2.0.
public class XmlForkedReader : System.Xml.XmlReader,
System.IDisposable
{
public XmlForkedReader(System.Xml.XmlReader reader,
System.Xml.XmlWriter forkedWriter) : base()
{
...
}
}
The whole trick lies in the Read
method. Depending on the XmlReader
's current node type, the corresponding operation is performed on the wrapped XmlReader
and the forked XmlWriter
used to log the message.
public override bool Read()
{
bool read = this.reader.Read();
switch (reader.NodeType)
{
case System.Xml.XmlNodeType.Element:
forkedWriter.WriteStartElement(reader.Prefix,
reader.LocalName, reader.NamespaceURI);
forkedWriter.WriteAttributes(reader,
this.writeDefaultAttributes);
if (reader.IsEmptyElement)
{
forkedWriter.WriteEndElement();
}
break;
case System.Xml.XmlNodeType.Text:
forkedWriter.WriteString(reader.Value);
break;
case System.Xml.XmlNodeType.Whitespace:
case System.Xml.XmlNodeType.SignificantWhitespace:
forkedWriter.WriteWhitespace(reader.Value);
break;
case System.Xml.XmlNodeType.CDATA:
forkedWriter.WriteCData(reader.Value);
break;
case System.Xml.XmlNodeType.EntityReference:
forkedWriter.WriteEntityRef(reader.Name);
break;
case System.Xml.XmlNodeType.XmlDeclaration:
case System.Xml.XmlNodeType.ProcessingInstruction:
forkedWriter.WriteProcessingInstruction(reader.Name,
reader.Value);
break;
case System.Xml.XmlNodeType.DocumentType:
forkedWriter.WriteDocType(reader.Name,
reader.GetAttribute("PUBLIC"),
reader.GetAttribute("SYSTEM"),
reader.Value);
break;
case System.Xml.XmlNodeType.Comment:
forkedWriter.WriteComment(reader.Value);
break;
case System.Xml.XmlNodeType.EndElement:
forkedWriter.WriteFullEndElement();
break;
}
return read;
}
The implementation of the other XmlReader
's members will only reflect the operations being performed on the wrapped XmlReader
.
Forking XML writing
To fork the writing operation of XML messages, I opted on building a XmlWriter
derived class (XmlForkedWriter
) that would receive, on instantiation of its objects, an instance of a XmlWriter
derived class used to write the message and an instance of a XmlWriter
derived class used to log the message.
For the same reasons as with the reader, I opted on implementing IDisposable
.
public class XmlForkedReader : System.Xml.XmlReader, System.IDisposable
{
public XmlForkedWriter(System.Xml.XmlWriter writer,
System.Xml.XmlWriter forkedWriter) : base()
{
...
}
}
The implementation of this class is simpler than the one for the reader because it only needs to duplicate the operations being performed on the XmlForkedWriter
in both the wrapped and forked XmlWriter
s.