Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

XmlSerializer: Serializing List of Interfaces

4.70/5 (9 votes)
3 Mar 2014CPOL1 min read 38K  
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:

C#
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
<?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 introduce is a new property on my Order that held a list in interfaces, something like shown below:

C#
{
    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 a 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 is implement the IXmlSerializable interface. So let's see what the new code would look like if we went down this path, shall we?

C#
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 and altered the Order class to use my new class. Then, I tried to serialize things again, and now I get this XML:

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 has been a while for me and 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:

C#
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
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)