XmlSerializer : Serializing list of interfaces

At work I was stuck with a small problem when working with the XmlSerializer which I have not been using that much of late. Anyway I started out with something like this small demo program below

class Program
{
    static void Main(string[] args)
    {

        Order order = new Order
        {
            Products = new List<Product> {
                new Product {
                    Id =1,
                    Name = "Dummy1",
                    Quantity=1
                }
            }
        };

        //serialize
        var xmlSerializer = new XmlSerializer(typeof(Order));
        var stringBuilder = new StringBuilder();
        var xmlTextWriter = XmlTextWriter.Create(stringBuilder,
            new XmlWriterSettings { NewLineChars = "\r\n", Indent = true });
        xmlSerializer.Serialize(xmlTextWriter, order);
        var finalXml = stringBuilder.ToString();

        //deserialize
        xmlSerializer = new XmlSerializer(typeof(Order));
        var xmlReader = XmlReader.Create(new StringReader(finalXml));
        var deserializedOrder = (Order)xmlSerializer.Deserialize(xmlReader);

        Console.ReadLine();
    }
}

[XmlRoot]
public class Order
{
    public List<Product> Products { get; set;}
}

Where I would get this Xml when I serialized the Order object I had in play.

<?xml version="1.0" encoding="utf-16"?>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Products>
    <Product>
      <Id>1</Id>
      <Name>Dummy1</Name>
      <Quantity>1</Quantity>
    </Product>
  </Products>
</Order>

And I would get this back when I deserialized the Xml back into an Order object

image

All cool so far. However what I then wanted to to introduce a new property on my Order that held a list in interfaces, something like shown below

{
    static void Main(string[] args)
    {

        Order order = new Order
        {
            Products = new List<Product> {
                new Product {
                    Id =1,
                    Name = "Dummy1",
                    Quantity=1
                }
            },
            Dispatchers = new List<IDispatcher> {
                new FileDispatcher()
            }
        };
    .....
    .....
    .....

        Console.ReadLine();
    }
}

[XmlRoot]
public class Order
{
    public List<Product> Products { get; set;}
    public List<IDispatcher> Dispatchers { get; set; }
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Quantity { get; set; }
}

public interface IDispatcher
{
    string Channel { get; set; }
    void Dispatch();
}

public class FileDispatcher : IDispatcher
{
    public string Channel { get; set; }

    public void Dispatch()
    {
        //This would do something in real code
    }
}

public class EmailDispatcher : IDispatcher
{
    public string Channel { get; set; }

    public void Dispatch()
    {
        //This would do something in real code
    }
}

So the code looks fine, it compiles nicely (always good start). So I then tried to serialize it, and got this

image

Mmm not great, so I had a think about this. Ok what about if we take control of the xml Serialization/Deserialization process ourselves. Should be easy enough to do, all we need to do it implement the IXmlSerializable interface. So lets see what the new code would look like if we went down this path shall we

public class Order
{
    public List<Product> Products { get; set;}
    public ListOfIDispatcher Dispatchers { get; set; }
}

public class ListOfIDispatcher : List<IDispatcher>, IXmlSerializable
{
    public ListOfIDispatcher() : base() { }

    public System.Xml.Schema.XmlSchema GetSchema() { return null; }

    public void ReadXml(XmlReader reader)
    {
        reader.ReadStartElement("Dispatchers");
        while (reader.IsStartElement("IDispatcher"))
        {
            Type type = Type.GetType(reader.GetAttribute("AssemblyQualifiedName"));
            XmlSerializer serial = new XmlSerializer(type);

            reader.ReadStartElement("IDispatcher");
            this.Add((IDispatcher)serial.Deserialize(reader));
            reader.ReadEndElement();
        }
        reader.ReadEndElement();
    }

    public void WriteXml(XmlWriter writer)
    {
        foreach (IDispatcher dispatcher in this)
        {
            writer.WriteStartElement("IDispatcher");
            writer.WriteAttributeString("AssemblyQualifiedName", dispatcher.GetType().AssemblyQualifiedName);
            XmlSerializer xmlSerializer = new XmlSerializer(dispatcher.GetType());
            xmlSerializer.Serialize(writer, dispatcher);
            writer.WriteEndElement();
        }
    }
}

So I added a new class an altered the Order class to use my new class. Then I tried to serialize things again, and now I get this XML

<?xml version="1.0" encoding="utf-16"?>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Products>
    <Product>
      <Id>1</Id>
      <Name>Dummy1</Name>
      <Quantity>1</Quantity>
    </Product>
  </Products>
  <Dispatchers>
    <IDispatcher AssemblyQualifiedName="XmlSerialization.FileDispatcher, XmlSerialization, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
      <FileDispatcher>
        <Channel>c:\temp\file001.txt</Channel>
      </FileDispatcher>
    </IDispatcher>
  </Dispatchers>
</Order>

Ah much better, but does it deserialize?…..Mmm well yes it does, here you go. As I say it have been a while for me an the XmlSerializer, I think if you used an abstract class and registered some known types somehow that would also work. Anyway I happy with this and hope it helps someone out there

image

For completeness here is the full listing of everything in its final state

class Program
{
    static void Main(string[] args)
    {

        Order order = new Order
        {
            Products = new List<Product> {
                new Product {
                    Id =1,
                    Name = "Dummy1",
                    Quantity=1
                }
            },
            Dispatchers = new ListOfIDispatcher {
                new FileDispatcher()
                {
                    Channel = @"c:\temp\file001.txt"
                }
            }
        };

        //serialize
        var xmlSerializer = new XmlSerializer(typeof(Order));
        var stringBuilder = new StringBuilder();
        var xmlTextWriter = XmlTextWriter.Create(stringBuilder,
            new XmlWriterSettings { NewLineChars = "\r\n", Indent = true });
        xmlSerializer.Serialize(xmlTextWriter, order);
        var finalXml = stringBuilder.ToString();

        //deserialize
        xmlSerializer = new XmlSerializer(typeof(Order));
        var xmlReader = XmlReader.Create(new StringReader(finalXml));
        var deserializedOrder = (Order)xmlSerializer.Deserialize(xmlReader);

        Console.ReadLine();
    }
}

[XmlRoot]
public class Order
{
    public List<Product> Products { get; set;}
    public ListOfIDispatcher Dispatchers { get; set; }
}

public class ListOfIDispatcher : List<IDispatcher>, IXmlSerializable
{
    public ListOfIDispatcher() : base() { }

    public System.Xml.Schema.XmlSchema GetSchema() { return null; }

    public void ReadXml(XmlReader reader)
    {
        reader.ReadStartElement("Dispatchers");
        while (reader.IsStartElement("IDispatcher"))
        {
            Type type = Type.GetType(reader.GetAttribute("AssemblyQualifiedName"));
            XmlSerializer serial = new XmlSerializer(type);

            reader.ReadStartElement("IDispatcher");
            this.Add((IDispatcher)serial.Deserialize(reader));
            reader.ReadEndElement();
        }
        reader.ReadEndElement();
    }

    public void WriteXml(XmlWriter writer)
    {
        foreach (IDispatcher dispatcher in this)
        {
            writer.WriteStartElement("IDispatcher");
            writer.WriteAttributeString("AssemblyQualifiedName", dispatcher.GetType().AssemblyQualifiedName);
            XmlSerializer xmlSerializer = new XmlSerializer(dispatcher.GetType());
            xmlSerializer.Serialize(writer, dispatcher);
            writer.WriteEndElement();
        }
    }
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Quantity { get; set; }
}

public interface IDispatcher
{
    string Channel { get; set; }
    void Dispatch();
}

public class FileDispatcher : IDispatcher
{
    public string Channel { get; set; }

    public void Dispatch()
    {
        //This would do something in real code
    }
}

public class EmailDispatcher : IDispatcher
{
    public string Channel { get; set; }

    public void Dispatch()
    {
        //This would do something in real code
    }
}
Advertisements

6 thoughts on “XmlSerializer : Serializing list of interfaces

  1. plamtod says:

    xmlSerializer object could be create once in WriteXml and ReadXml before foreach , isnt it?

    • sachabarber says:

      No it needs to in the body of the for loop, as ity uses the current loop read type. You could move the variable definition outside the look, but, mphhhff

  2. Nice post! I found a ton of examples on how to use a base class instead of an interface. But that’s not what I wanted! I wanted to use interfaces! This seems to do the trick – now I have to implement my version of it 😉

  3. joergflechsig says:

    If you are working with a recursive structure though, you have to terminate self closing tags when using a custom deserializer like this, because they will be read as closing tags instead. Just wanted to tell, because I just wasted 3 hours of my life.
    Thanks for this solution nonetheless! (great fan :P)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: