Implementing IXmlSerializable

You may or may not have noticed, but there are some CLR types that just won’t serialize for WebServices. One of these is System.Drawing.Color. If your class has a property of this type, it will mysteriously appear on the client-side as Black. No errors, just Black. The reason for this is that the XmlSerializer iterates through the public instance read/write properties for a given type to create the child elements. Unfortunately, System.Drawing.Color does not have any instance read/write properties. So, I decided to create a simple type that contains an instance of System.Drawing.Color that does expose a read/write property (you see, you can’t inherit from System.Drawing.Color). It worked, and all was good. Then I decided to be cute and make my type interchangeable with System.Drawing.Color by adding an implicit conversion. Unfortunately, that broke the XmlSerializer – apparently having a implicit converter that converts from System.Drawing.Color to MyNamespace.Color confuses the XmlSerializer. But I really wanted that implicit conversion. So, I decided to grit my teeth and implement IXmlSerializable.

If you were to look at the source code for the IXmlSerializable interface, you’d probably see the following:
[csharp]
interface IXmlSerializable
{
System.Xml.Schema.XmlSchema GetSchema ( )
void ReadXml ( System.Xml.XmlReader reader )
void WriteXml ( System.Xml.XmlWriter writer )
}
[/csharp]

This interface is actually quite simple to implement. GetSchema simply wants you to define the XML schema for your type’s XML (this is used for WebService WSDL generation), WriteXml is used to generate the XML stream that the object is being serialized into, and ReadXml is used to (you guessed it!) read the XML stream to populate a new instance of the object.

So! Without further ado, let’s walk through a sample implementation:

Let’s define a class that contains one of our problematic types as a public property.
[csharp]
public class Pencil
{
String manufacturer;
System.Drawing.Color leadColor;
float eraserLength;

public Pencil() : this(String.Empty, System.Drawing.Color.Black, 0.0f)
{
}
public Pencil(String manufacturer, System.Drawing.Color leadColor, float eraserLength)
{
this.manufacturer = manufacturer;
this.leadColor = leadColor;
this.eraserLength = eraserLength;
}
public String Manufacturer
{
get
{
return this.manufacturer;
}
}
public System.Drawing.Color LeadColor
{
get
{
return this.leadColor;
}
set
{
this.leadColor = value;
}
}
public float EraserLength
{
get
{
return this.eraserLength;
}
set
{
this.eraserLength = value;
}
}
}
[/csharp]

Let’s serialize this class to an xml file. To do this, we need to create an instance of System.Xml.Serialization.XmlSerializer that’s geared towards serializing our type, create an XmlWriter, and use the XmlSerializer to serialize the object to the XmlWriter:
[csharp]

Pencil myPencil = new Pencil(“Andy”, System.Drawing.Color.Blue, 1.5f);

System.Xml.Serialization.XmlSerializer serializer = null;
serializer = new System.Xml.Serialization.XmlSerializer(myPencil.GetType());
System.Xml.XmlTextWriter writer;
writer = new System.Xml.XmlTextWriter(
“C:\\Temp\\Serialization.xml”,
System.Text.Encoding.ASCII
);
serializer.Serialize(writer, myPencil);
writer.Close();

[/csharp]
If you were to serialize this class using an XmlSerializer, you’d see:
[xml]
< ?xml version="1.0" encoding="us-ascii"?>

xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”>

1.5
[/xml]
As you can see, our color information didn’t get serialized properly, and read-only property didn’t serialize at all. If we were to deserialize this object, we’d have only a partial copy of the information that was in the original; namely, the color would be Black, and the ManufacturerName property would be an empty string.

In order to get our object to serialize correctly, let’s add support for IXmlSerializable. GetSchema entails the most work of all of the methods, and even that is not terribly hard. The main requirements are that the schema reflects the XML your WriteXml method will generate, and that a schema ID is supplied. Providing a target namespace is also a good idea, as it is used in the WSDL generated for the WebService.

First, let’s take a look at what our type’s schema should look like. In this case, we want a parent element (Pencil) that contains a set of child elements corresponding to the properties (Manufacturer, LeadColor, EraserLength), making it a complex type. Since we know the properties will always exist, and our serialization code will always emit the elements in the same order, we’ll specify that the child elements are in a sequence. Finally, we’ll specify what the simple types of the (non-complex) child elements are.
[xml]








[/xml]
Now, we could simply save the above schema to a file and load it using XmlSchema.Load(…), but it’s much more fun to learn how to use the Framework to do these things, isn’t it? The System.Xml.Schema namespace provides programmatic support for generating just such a schema. The XmlSchema type is the equivalent of the element in the above schema. We’ll create an instance of this type and set the schema’s ID and target namespace using the object’s similarly-named properties. Next, we’ll create the schema’s one and only child element: our Pencil object’s XML equivalent, the element. This element is going to contain children, so it will by definition be a complex type. We could be thorough and define a separate complex type and inherit from it by assigning the complex type to the SchemaType property, but it’s not absolutely necessary. Since we are not going to reference this complex type more than once, it’s OK to simply directly define the complex content inside the parent element. Delving a bit deeper, we have the choice of group, choice, all, or sequence. Since the properties will always be present and our serializer code will always emit the elements in the same order, we’ll use a sequence and assign it to the complex type’s Particle property. Finally, we need to define the child elements representing the properties and their simple type and add them to the sequence.

Now that we have defined our schema, we can perform the tasks of serializing and deserializing the object to an XML stream. In both vases, the schema you defined in GetSchema should dictate the sequence in which you write and read the information from the stream. Since we defined the schema to be a main element that contains a sequence of child elements, we’ll write the XML in the same order: we’ll open a Pencil element, then write the Manufacturer, LeadColor, and EraserLength elements (in that order). I decided to represent the color as an HTML colorref, so I used String.Format(…) to generate the content for the LeadColor elelemt. Both of the other elements used the ToString() method to generate their elements’ content. Reading from the stream is just as simple, but I did have to use a combination of a regular expression and Convert.ToByte(…) to load my color’s red, green, and blue components from the element.
[csharp]
public class Pencil : System.Xml.Serialization.IXmlSerializable
{
String manufacturer;
System.Drawing.Color leadColor;
float eraserLength;

public Pencil() : this(String.Empty, System.Drawing.Color.Black, 0.0f)
{
}
public Pencil(String manufacturer, System.Drawing.Color leadColor, float eraserLength)
{
this.manufacturer = manufacturer;
this.leadColor = leadColor;
this.eraserLength = eraserLength;
}
public String Manufacturer
{
get
{
return this.manufacturer;
}
}
public System.Drawing.Color LeadColor
{
get
{
return this.leadColor;
}
set
{
this.leadColor = value;
}
}
public float EraserLength
{
get
{
return this.eraserLength;
}
set
{
this.eraserLength = value;
}
}

#region IXmlSerializable Members

System.Xml.Schema.XmlSchema System.Xml.Serialization.IXmlSerializable.GetSchema()
{
String namespace;
System.Xml.Schema.XmlSchema output;
System.Xml.Schema.XmlSchemaElement pencil, manufacturer, leadColor, eraserLength;
System.Xml.Schema.XmlSchemaComplexType pencilType;
System.Xml.Schema.XmlSchemaSequence sequence;

namespace = “http://hoppersoft.com/schemas/Pencil”
output = new System.Xml.Schema.XmlSchema();
output.Namespaces.Add(“xsd”, “http://www.w3.org/2001/XMLSchema”);
output.Namespaces.Add(“xsi”, “http://www.w3.org/2001/XMLSchema-instance”);
output.Namespaces.Add(“tns”, namespace);
output.Id = “PencilSchema”;
output.TargetNamespace = namespace;

// Define the Pencil element (the primary element for this schema)
pencil = new System.Xml.Schema.XmlSchemaElement();
pencil.Name = “Pencil”;
// Define the type for this element – since we have child nodes, it’s complex
pencilType = new System.Xml.Schema.XmlSchemaComplexType();
// The properties will always be supplied in the same order, so we’ll use a sequence
sequence = new System.Xml.Schema.XmlSchemaSequence();

// Now, let’s add the elements in the sequence

// Manufacturer
manufacturer = new System.Xml.Schema.XmlSchemaElement();
manufacturer.Name = “Manufacturer”;
manufacturer.SchemaTypeName = new System.Xml.XmlQualifiedName(
“string”,
“http://www.w3.org/2001/XMLSchema”
);
sequence.Items.Add(manufacturer);

// LeadColor
leadColor = new System.Xml.Schema.XmlSchemaElement();
leadColor.Name = “LeadColor”;
leadColor.SchemaTypeName = new System.Xml.XmlQualifiedName(
“string”,
“http://www.w3.org/2001/XMLSchema”
);
sequence.Items.Add(leadColor);

// EraserLength
eraserLength = new System.Xml.Schema.XmlSchemaElement();
eraserLength.Name = “EraserLength”;
eraserLength.SchemaTypeName = new System.Xml.XmlQualifiedName(
“float”,
“http://www.w3.org/2001/XMLSchema”
);
sequence.Items.Add(eraserLength);

// Attach this sequence to the complex type
pencilType.Particle = sequence;
// Assign the schema type
pencil.SchemaType = pencilType;

output.Items.Add(pencil);

output.Compile(new System.Xml.Schema.ValidationEventHandler(this.ValidationCallback));
return output;
}
void ValidationCallback(object sender, System.Xml.Schema.ValidationEventArgs args)
{
// I’m a very bad person for not doing anything here…
System.Diagnostics.Trace.WriteLine(args.Message);
}
void System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement(“Pencil”);

writer.WriteStartElement(“Manufacturer”);
writer.WriteString(this.manufacturer);
writer.WriteEndElement();

writer.WriteStartElement(“LeadColor”);
writer.WriteString(
String.Format(“#{0:X2}{1:X2}{2:X2}”,
this.leadColor.R, this.leadColor.G, this.leadColor.B));
writer.WriteEndElement();

writer.WriteStartElement(“EraserLength”);
writer.WriteString(this.eraserLength.ToString());
writer.WriteEndElement();

writer.WriteEndElement();
}
void System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
String colorRef;
System.Text.RegularExpressions.Match match;
Byte red, green, blue;

// Read the opening tag of the encapsulating element
reader.ReadStartElement();

// Open the root element for our object
reader.ReadStartElement(“Pencil”);

reader.ReadStartElement(“Manufacturer”);
this.manufacturer = reader.ReadString();
reader.ReadEndElement();

reader.ReadStartElement(“LeadColor”);
colorRef = reader.ReadString();
match = System.Text.RegularExpressions.Regex.Match(
colorRef,
“#(?[A-F0-9]{2})(?[A-F0-9]{2})(?[A-F0-9]{2})”
);
if(match.Success)
{
red = Convert.ToByte(match.Groups["red"].Value, 16);
green = Convert.ToByte(match.Groups["green"].Value, 16);
blue = Convert.ToByte(match.Groups["blue"].Value, 16);
this.leadColor = System.Drawing.Color.FromArgb(red, green, blue);
}
reader.ReadEndElement();

reader.ReadStartElement(“EraserLength”);
this.eraserLength = float.Parse(reader.ReadString());
reader.ReadEndElement();

// Close the root element for our object
reader.ReadEndElement();

// Read the end tag of the encapsulating element
reader.ReadEndElement();
}

#endregion // IXmlSerializable Members
}
[/csharp]
At this point, we are ready to test our serialization code. If you use the code snippet above to serialize the object to an XML file again, you will see our object, happily serialized to an XML document.

< ?xml version="1.0" encoding="us-ascii" ?>

Andy
#0000FF
1.5

Notice anything out of the ordinary? Yes, the above is not a typo; objects that implement IXmlSerializable are not going to generate the kind of XML you’d normally expect. This is due to the fact that the schema for our type is essentially externalized by the standard serialization framework, and rather than referencing the schema our IXmlSerializable-derived type generates to set the containing element’s type, we are stuck with an XSD ‘any’:






Rather than the type reference we expected to see:


xmlns:q1=”http://hoppersoft.com/schemas/pencil”
type=”q1:Pencil” />

Since we have no control over the generation of this schema, we are left with the default maxOccurs and minOccurs of 1 on the ‘any’ element, so defining a schema with multiple top-level elements does us no good here. This forces us to use a ‘root element’ approach to our object’s serialization – there will always be an element that encapsulates our object’s XML, and the object’s XML is comprised of a root element containing child elements populated with our object’s instance data.

I can understand why Microsoft decided not to document this interface. There are many pitfalls, here. A firm understanding of XML schemas is necessary to successfully pull it off, and once you start writing your type’s XML, you’re pretty much committed to writing the XML for the entire object graph. That being said, there is considerable power here, and may come in handy when you find yourself wanting to “tweak” the XML generated for your type when it is serialized.

Stay tuned for a future entry, in which we will discuss how to serialize and deserialize complex object graphs including objects that themselves implement IXmlSerializable.

Related Posts

Why using this?

On the SA1101 rule in the source analysis tool which seems to have a problem with 'this'.

Read more

Linq To Sql or Entity Framework?

Read more

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

top