The XmlSerializer works fine as long as you work with concrete classes. But soon as you specify an interface within an object the serialization will fail with the message “Cannot serialize member …” But there’s a solution to hack this.
Have a look at the following example classes.
public interface ISerializedItem
{
string Name { get; set; }
}
public class ItemA : ISerializedItem { ... }
public class ItemB : ISerializedItem { ... }
public class ToSerialize
{
public List<ISerializedItem> Items { get; set; }
}
When you’re trying to serialize such objects, you will get an exception. The workaround is to implement the IXmlSerializable interface.
There’re three methods to implement:
- public XmlSchema GetSchema()
- public void ReadXml(XmlReader reader)
- public void WriteXml(XmlWriter writer)
The GetSchema can return NULL – it’s not used in our case. The ReadXml and WriteXml Method must implement the serialization. As long this is not trivial here’s the complete code for the example above.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Xml.XPath;
namespace GenericInterfaceXmlSerializer
{
public interface ISerializedItem
{
string Name { get; set; }
}
public class ItemA : ISerializedItem
{
public string Name { get; set; }
}
public class ItemB : ISerializedItem
{
public string Name { get; set; }
}
public class ToSerialize : IXmlSerializable
{
public List<ISerializedItem> Items { get; set; }
public ToSerialize()
{
Items = new List<ISerializedItem>();
}
#region IXmlSerializable implementation
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
// Create a XPathDocument out of the Xml in order to navigate through
XPathDocument xPath = new XPathDocument(reader);
XPathNavigator navigator = xPath.CreateNavigator();
// Iterate through all elements of Items
XPathNodeIterator iterator = navigator.Select("//ToSerialize/Items/Element");
while (iterator.MoveNext())
{
Dictionary<string, string> values = ReadNodes(iterator.Current);
// Create element of the specified type
ISerializedItem element = (ISerializedItem)Activator.CreateInstance(Type.GetType(values["Type"]));
element.Name = values["Name"];
Items.Add(element);
}
}
public void WriteXml(XmlWriter writer)
{
// Create a new Xml Document
XmlDocument xdoc = new XmlDocument();
// Iterate trough all Items and append them
XmlNode items = xdoc.AppendChild(CreateNode(xdoc, "Items", string.Empty));
foreach (ISerializedItem item in Items)
{
XmlNode element = items.AppendChild(CreateNode(xdoc, "Element", string.Empty));
element.AppendChild(CreateNode(xdoc, "Type", item.GetType().FullName));
element.AppendChild(CreateNode(xdoc, "Name", item.Name));
}
// Output XML
xdoc.WriteTo(writer);
}
private static XmlNode CreateNode(XmlDocument doc, string name, string value)
{
XmlNode node = doc.CreateNode(XmlNodeType.Element, name, string.Empty);
if (!string.IsNullOrEmpty(value))
node.AppendChild(doc.CreateTextNode(value));
return node;
}
private static Dictionary<string, string> ReadNodes(XPathNavigator navigator)
{
Dictionary<string, string> result = new Dictionary<string, string>();
if (!navigator.MoveToFirstChild())
return result;
do { result.Add(navigator.Name, navigator.Value); } while (navigator.MoveToNext());
navigator.MoveToParent();
return result;
}
#endregion
}
class Program
{
/// <summary>
/// Example Code
/// </summary>
static void Main()
{
try
{
ToSerialize toSerialize = new ToSerialize();
toSerialize.Items.Add(new ItemA{Name="Cat"});
toSerialize.Items.Add(new ItemB{Name="Dog"});
XmlSerializer serializer = new XmlSerializer(typeof(ToSerialize));
XmlTextWriter writer = new XmlTextWriter("test.xml", Encoding.UTF8);
serializer.Serialize(writer, toSerialize);
writer.Close();
XmlTextReader reader = new XmlTextReader("test.xml");
ToSerialize result = (ToSerialize) serializer.Deserialize(reader);
reader.Close();
Debug.Assert(toSerialize.Items.Count == result.Items.Count, "Count does not match");
}
catch (Exception exc)
{
do
{
Console.Error.WriteLine(exc.Message);
exc = exc.InnerException;
} while (exc != null);
}
}
}
}
The trick is, to serialize also the type of the element within the collection. As you can see the ReadXml method first reads the Type of the element and creates an object of that type by using the Activator.
ISerializedItem element = (ISerializedItem)Activator.CreateInstance(Type.GetType(values["Type"]));
After that the interface properties can be set with the values of the de-serialization.
Pretty cool I think.
Cheers
- Gerhard