It's surprising that XmlDocument isn't marked [Serializable], because it's very natural to serialize one into a stream. I wanted to put an object into ASP.NET ViewState the other day, and quickly ran into this roadblock, because part of the object included an XmlDocument, which is not serializable. A quick search revealed that most people deal with this problem by storing a string instead. Indeed, that was where I started, but I quickly realized that there are multiple places in my code where I want to do this sort of thing, and I don't want to have to mess with it in each data structure that contains an XmlDocument.
So I put together a simple class that holds an XmlDocument and implements ISerializable and called it SerializableXmlDocument. I'm sharing the source code here in the hopes that
a) somebody will find it useful, and
b) somebody smarter than I am will point out how I screwed it up and help me make it better.
SerializableXmlDocument includes implicit conversion operators to make it easy to convert to/from an XmlDocument. It holds the actual document in a property called Value. This "isomorph" pattern is one that I picked up from Craig.
While writing this code, I also wrote a helpful extension method for getting a byte array out of a MemoryStream that is exactly the length of the data written to the stream so far (CopyUpToSeekPointer). So don't go looking in the docs for MemoryStream for this method :) This is obviously not the most efficient way to consume bytes written to a MemoryStream since it copies the data into a new byte array, but it's very convenient in many scenarios.
Here is SerializableXmlDocument.cs:
using System;
using System.Runtime.Serialization;
using System.Xml;
using System.IO;
namespace Pluralsight.Samples
{
[Serializable]
public class SerializableXmlDocument : ISerializable
{
public SerializableXmlDocument() { }
public SerializableXmlDocument(XmlDocument value)
{
this.Value = value;
}
public XmlDocument Value { get; set; }
#region ISerializable implementation
public SerializableXmlDocument(SerializationInfo info,
StreamingContext context)
{
byte[] serializedData = (byte[])info.GetValue("doc",
typeof(byte[]));
if (null != serializedData)
this.Value = Deserialize(serializedData);
}
public void GetObjectData(SerializationInfo info,
StreamingContext context)
{
byte[] serializedData = null;
if (null != Value)
serializedData = Serialize(Value);
info.AddValue("doc", serializedData);
}
#endregion
#region implicit conversion to/from XmlDocument
public static implicit operator SerializableXmlDocument(
XmlDocument doc)
{
return new SerializableXmlDocument(doc);
}
public static implicit operator XmlDocument(
SerializableXmlDocument sdoc)
{
return sdoc.Value;
}
#endregion
#region Xml serialization helper methods
private static byte[] Serialize(XmlDocument doc)
{
MemoryStream stream = new MemoryStream();
doc.Save(stream);
return stream.CopyUpToSeekPointer();
}
private static XmlDocument Deserialize(byte[] serializedData)
{
XmlDocument doc = new XmlDocument();
doc.Load(new MemoryStream(serializedData, false));
return doc;
}
#endregion
}
}
...and here's the CopyUpToSeekPointer extension method for MemoryStream:
using System;
using System.IO;
namespace Pluralsight.Samples
{
public static class MemoryStreamExtensionMethods
{
public static byte[] CopyUpToSeekPointer(
this MemoryStream stream)
{
// copy only the part of the buffer
// that contains the serialized document
long length = stream.Position;
byte[] buffer = stream.GetBuffer();
byte[] result = new byte[length];
for (int i = 0; i < length; ++i)
result[i] = buffer[i];
return result;
}
}
}
...and here's a sample object that uses SerializableXmlDocument:
using System;
namespace Pluralsight.Samples
{
[Serializable]
public class Item
{
public string Name { get; set; }
public SerializableXmlDocument Data { get; set; }
public void Print()
{
Console.WriteLine("Name: {0}", Name);
Console.WriteLine(Data.Value.OuterXml);
}
}
}
...and here's a sample program that creates an instance of Item, serializes it, then deserializes it, printing diagnostics along the way to show that it's working properly.
using System;
using System.Xml;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using Pluralsight.Samples;
class DemoProgram
{
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml("<root><child>text</child></root>");
Item item = new Item
{
Name = "Testing 123",
Data = doc,
};
// print object before serialization
item.Print();
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, item);
byte[] serializedItem = stream.CopyUpToSeekPointer();
Console.WriteLine("Serialized data (base64): {0}",
Convert.ToBase64String(serializedItem));
item = (Item)formatter.Deserialize(
new MemoryStream(serializedItem, false));
// print object after deserialization
item.Print();
}
}
Here's the output of the previous sample program:
Flame away!





