Serializable XmlDocument

Security Briefs

Syndication

(Updated 9 Oct 2008: replaced my custom MemoryStream.CopyUpToSeekPointer() extension method with MemoryStream.ToArray(), a built in method on MemoryStream that I overlooked and should have been using)

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.

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.ToArray();
}
private static XmlDocument Deserialize(byte[] serializedData)
{
XmlDocument doc = new XmlDocument();
doc.Load(new MemoryStream(serializedData, false));
return doc;
}
#endregion
}
}

...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:

sample-output 

Flame away!


Posted Aug 18 2008, 08:58 PM by keith-brown
Filed under: ,

Comments

Jon Flanders wrote re: Serializable XmlDocument
on 08-19-2008 8:00 AM

Another nice way to deal with this is to create a  serialization surrogate - www.masteringbiztalk.com/.../PermaLink,guid,5f4d8c41-73bf-4d7f-93b4-8934130a783b.aspx.

I think your way is best if you are only ever going to have to serialize one non-Serializable type.  But if you are going to have more than one I think it makes sense to go down the ISerializationSurrogate route.  This is what WF does btw - and how Activity (and XmlDocument since they built a SerializationSurrogate for XmlDocument inside of the WF  runtime for the same reasons you state here).

keith-brown wrote re: Serializable XmlDocument
on 08-19-2008 9:44 AM

Jon,

Yep my original goal was to use the serialization surrogate infrastructure. The problem I ran into with ViewState is that I couldn't find any way to register a surrogate selector - perhaps it exists, but I didn't find it in my searches. Reading your blog post makes it clear that WF exposes a way to chain in a surrogate selector.

So SerializableXmlDocument fills in the gaps when you can't register a surrogate selector.

Jon Flanders wrote re: Serializable XmlDocument
on 08-19-2008 10:03 AM

Oh yea - that's true.  They don't expose that in their model.  Unfortunate.

Jimmy Zimms wrote re: Serializable XmlDocument
on 08-19-2008 10:24 AM

Keith,

Yup you are right. Hooking into the serializer to supply a surrogate is the correct way to do it but as you state, there's just no way to register them in ASP.Net AFAIK too.

Duncan Smart wrote re: Serializable XmlDocument
on 08-20-2008 5:17 AM

We went for the simple string approach that you mentioned, as we could easily update our code to create new SerializableXmlDocuments rarther than the plain old XmlDocument.

[Serializable]

public class SerializableXmlDocument : XmlDocument, ISerializable

{

public SerializableXmlDocument()

{

}

protected SerializableXmlDocument(SerializationInfo info, StreamingContext context)

{

this.InnerXml = info.GetString("XML");

}

void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)

{

info.AddValue("XML", this.InnerXml);

}

}

Add a Comment

(required)  
(optional)
(required)  
Remember Me?