Custom XML Serialization for the .NET Compact Framework

February 28, 2007 - 6 minute read -
compact framework xml windows-mobile

.NET provides a whole slew of utilities for serializing objects into an XML form. But as I wrote in my previous post, .NET Compact Framework has serious problems with this serialization. The good news is that you can leverage all of the existing Attributes and tricks that you think should work (if it weren't so buggy) and use them in your own serialization scheme.

Get Started

For example I want to know if I should skip a given member? There are a number of different things I can check. Is a Reference type null? Is there and XmlIgnore attribute? Is there a PropertyNameSpecified value set to false? All of those questions can easily be answered using reflection.

/// <summary>
/// Should the current property be skipped based on rules
/// such as the existence of a propertySpecified value set to false?
/// </summary>
/// <param name="member">The MemberInfo to check</param>
/// <param name="o">The object that contained this member</param>
/// <returns>true if this member should be skipped</returns>
public bool SkipMember(MemberInfo member, object o)
{
    object val = null;
    if (member.MemberType == MemberTypes.Field)
    {
        val = ((FieldInfo)member).GetValue(o);
    }
    else if (member.MemberType == MemberTypes.Property)
    {
        val = ((PropertyInfo)member).GetValue(o, null);
    }
    if (null == val)
      return true;

    string propertyToTest = member.Name + "Specified";
    PropertyInfo specifiedProperty = o.GetType().GetProperty(propertyToTest);
    if ((null != specifiedProperty && !(bool)specifiedProperty.GetValue(o, null)))
        return true;

    FieldInfo specifiedField = o.GetType().GetField(propertyToTest, FIELD_BINDING_FLAGS);
    if ((null != specifiedField && !(bool)specifiedField.GetValue(o)))
        return true;

    return member.IsDefined(typeof(XmlIgnoreAttribute), false);
}

I can use a similar "fall-through" strategy to determine the name of the element to write using the XmlElement attribute for example. Now that I know I can answer some basic questions about an Object using the built-in mechanisms that .NET uses for serialization I can get down to serious serialization.

We're all Object-Oriented programmers these days right? Right!? So to start I decided that the best way to handle this problem was to decompose it into a bunch of simpler problems.

ITagWriter

There are two things that we can write in XML. Either an XML Element or an XML Attribute. So, I created an interface ITagWriter with two concrete implementations to correspond to these two XML types: AttributeTagWriter and ElementTagWriter. These classes allow me to write the structure of the XML Document.

/// <summary>
/// Interface to implement to write different Xml tags
/// Either Elements or Attributes.
/// </summary>
internal interface ITagWriter
{
    /// <summary>
    /// Write the opening Xml tag with the given name
    /// </summary>
    /// <param name="doc">The XML Document to write the tage to.</param>
    /// <param name="tagName">The name of the tag</param>
    void WriteStart(XmlWriter doc, string tagName);
    /// <summary>
    /// Write the appropriate end tag
    /// </summary>
    /// <param name="doc">The XML Document to write the tage to.</param>
    void WriteEnd(XmlWriter doc);
}

IValueWriter

With the ability to write the structure, I then need to be able to write out the values of the various objects and their properties. Just like with the ITagWriter interface, I decided to create an IValueWriter for the various kinds of values that I would need to write. The types I came up with were ObjectWriter, CollectionValueWriter, EnumValueWriter, SimpleValueWriter, and XmlElementValueWriter.

/// <summary>
/// Interface to implement to write different kinds of values.
/// </summary>
internal interface IValueWriter
{
    /// <summary>
    /// Write the Entry value to the XmlDocument
    /// </summary>
    /// <param name="doc">The XML Document to write the tage to.</param>
    /// <param name="entry">The meta-information and value to write.</param>
    void Write(XmlWriter doc, CustomSerializationEntry entry);
}

You'll notice the CustomSerializationEntry class is the parameter for the IValueWriter.Write() method. This class contains all of the metadata and the value about the various properties of an Object. This alows us an easy way to ask questions about a given property. Is it a Collection? Is it an Enum? Is there a sort order? Basically the idea is to encapsulate all of the things that are interesting from a serialization point of view.

To help manage the interaction I also created a basic TypeLookup class. The job of this class is to determine what type of ITagWriter and IValueWriter to use for a given CustomSerializationEntry instance. This allows us to centralize that decision making in a single class. The centralized knowledge keeps the individual writer implementations much simpler. They just need to ask for the correct writer and then call the methods defined in the interface. They don't need to care what type they are writing. All hail power of encapsulation and abstraction!

Start Serializing

I bootstrap the serialization by creating an ObjectWriter to handle the outermost object. From there, the ObjectWriter takes over, constructing CustomSerializationEntry objects for each of the serialized object's properties. The type of the property determines the type of IValueWriter that is used to write the property value.

/// <summary>
/// Serialize an object using the given
/// <paramref name="xmlRoot">xmlRoot</paramref> as the root element name.
/// </summary>
/// <param name="o"></param>
/// <param name="xmlRoot"></param>
/// <returns></returns>
public string Serialize(object o, string xmlRoot)
{
    StringBuilder sb = new StringBuilder();
    using (XmlTextWriter writer = new XmlTextWriter(new StringWriter(sb)))
    {
        writer.Formatting = Formatting.Indented;
        XmlWriter xmlDoc = XmlWriter.Create(writer);
        WriteRootElement(xmlDoc, o, xmlRoot);
    }
    return sb.ToString();
}
private static void WriteRootElement(XmlWriter doc, object o, string rootElement)
{
    doc.WriteStartDocument();
    ObjectWriter writer = new ObjectWriter(new TypeLookup());
    writer.Write(doc, o, rootElement);
    doc.WriteEndDocument();
}

The ObjectWriter itself creates a CustomSerializationEntry for all the properties that should be written. It then loops over the properties. Notice how it uses the TypeLookup (lookup) to ask for the proper value writer for each of the properties.

// ...
        public void Write(XmlWriter doc, object o, string elementName)
        {
            doc.WriteStartElement(elementName);
            IEnumerable<CustomSerializationEntry> entries = GetMemberInfo(o);
            foreach (CustomSerializationEntry currentEntry in entries)
            {
                lookup.GetValueWriter(currentEntry).Write(doc, currentEntry);
            }
            doc.WriteEndElement();
        }
// ...

Conclusion

OK, so I left out a lot of details! But If I gave you all of the answers it wouldn't be any fun now would it. I hope you can see how decomposing the problem of serialization turns it a series of relatively simple problems that you can answer. So, if I can do this in about 500 lines of code, how come Microsoft can't implement a decent XML Serializer for the .NET Compact Framework.