navigation
 Tuesday, November 07, 2006

Serializing objects to XML and back again in C# is trivial until you need greater control over the operation.  How do you serialize binary data, Color properties or some object type that hasn't been invented yet?  Implementing IXmlSerializable allows you to read and write object data in whatever format and by any means you choose. 

The example serializes a list of objects with a single Color property. 

<?xml version="1.0" ?>
<Items>
    <Item>Red</Item>
    <Item>White</Item>
    <Item>Blue</Item>
</Items>

The sample below serializes "MyObjects.xml" from disk to populate a MYObjectList object, makes changes to the list and re-writes "MyObjects.xml".  The two important IXmlSerializable methods to override are ReadXml() and WriteXml().  ReadXml() consumes an XmlReader instance.  Here we use a XmlTextReader to pull the xml text off the disk.  Likewise, WriteXml() uses a XmlTextWriter instance to recreate the xml file.
 
class Program
{
  static void Main(string[] args)
  {
    MyObjectList myObjectList = new MyObjectList();

    XmlTextReader reader = new XmlTextReader("MyObjects.xml");
    myObjectList.ReadXml(reader);

    myObjectList.Items.Clear();
    myObjectList.Items.Add(new MyObject(Color.BlanchedAlmond));
    myObjectList.Items.Add(new MyObject(Color.Blue));
    myObjectList.Items.Add(new MyObject(Color.LightSlateGray));

    XmlTextWriter writer = new XmlTextWriter("MyObjects.xml", null);
    writer.Formatting = Formatting.Indented;
    myObjectList.WriteXml(writer);
  }
}
 
<?xml version="1.0" ?>
<Items>
    <Item>BlanchedAlmond</Item>
    <Item>Blue</Item>
    <Item>LightSlateGray</Item>
</Items>
 
The sample is straightforward but you can use the same technique with data of any complexity.  "MyObjectList" contains a List<>, but that fact turns out to be immaterial.  The core idea is that implementing IXMLSerializable ReadXML() and WriteXML() can get the job done no matter what the data is or what format it needs to be saved as.  Here is the full source for the example, followed by an example with a slightly more complex xml structure.

using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Drawing;

namespace SerializeGenerics1
{

  public class MyObject
  {
    public MyObject(Color MyColor)
    {
      _myColor = MyColor;
    }

    private Color _myColor;
    public Color MyColor
    {
      get { return _myColor; }
      set { _myColor = value; }
    }
  }

  public class MyObjectList
  {
    List<MyObject> _items = new List<MyObject>();

    public List<MyObject> Items
    {
      get { return _items; }
      set { _items = value; }
    }

#region IXmlSerializable Members

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

    public void ReadXml(System.Xml.XmlReader reader)
    {
      _items.Clear();
      while (!reader.EOF)
      {
        if (reader.ReadToFollowing("Item"))
        _items.Add(new MyObject(Color.FromName(reader.ReadString())));
      }
      reader.Close(); 
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
      writer.WriteStartDocument();

      writer.WriteStartElement("Items");

      foreach (MyObject myObject in _items)
      {
        if (myObject.MyColor.IsKnownColor)
        writer.WriteElementString("Item", myObject.MyColor.Name);
      }

      writer.WriteEndElement(); // close Items tag

      writer.WriteEndDocument();

      writer.Close();
    }

#endregion
}

class Program
{
  static void Main(string[] args)
  {
    MyObjectList myObjectList = new MyObjectList();

    XmlTextReader reader = new XmlTextReader("MyObjects.xml");
    myObjectList.ReadXml(reader);

    myObjectList.Items.Clear();
    myObjectList.Items.Add(new MyObject(Color.BlanchedAlmond));
    myObjectList.Items.Add(new MyObject(Color.Blue));
    myObjectList.Items.Add(new MyObject(Color.LightSlateGray));

    XmlTextWriter writer = new XmlTextWriter("MyObjects.xml", null);
    writer.Formatting = Formatting.Indented;
    myObjectList.WriteXml(writer);
  }
 }
}

This next example serializes settings for a "Wheel of Fortune" style game written in WPF.   The "Color" referenced here is from the Systems.Windows.Media namespace and doesn't have a "Name" property as in the previous example.  The xml file is nested with a Settings/Setting/<properties> structure:

<?xml version="1.0" ?>
<Settings>
  <Setting>
    <Percentage>10</Percentage> 
    <Color>#FFFF0000</Color> 
    <Title>Nice Try</Title> 
    <Description /> 
    <IsWinner>False</IsWinner>
  </Setting>
  <Setting>
    <Percentage>10</Percentage> 
    <Color>#FFB0E0E6</Color> 
    <Title>T Shirt</Title> 
    <Description>T-Shirt</Description> 
    <IsWinner>True</IsWinner> 
  </Setting>
</Settings>

The IXmlSerializable implementation:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.Windows.Media;
using System.ComponentModel;
using System.Diagnostics;

namespace Falafel.Training.WPF
{
  public class Settings : IXmlSerializable, IEnumerable
  {
    List<Setting> _items = new List<Setting>();

  public void Add(Setting setting)
  {
    _items.Add(setting);
  }

#region IXmlSerializable Members

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

  public void ReadXml(System.Xml.XmlReader reader)
  {
    try
    {
      XmlTextReader textReader = reader as XmlTextReader;
      textReader.WhitespaceHandling = WhitespaceHandling.None;

      _items.Clear();

      while (textReader.ReadToFollowing("Setting"))
      {
        Setting setting = new Setting();
        textReader.Read();
        setting.Percentage = Convert.ToInt32(textReader.ReadString());
        textReader.Read();
        setting.Color = (Color)TypeDescriptor.GetConverter(typeof(Color)).ConvertFromString(textReader.ReadString());
        textReader.Read();
        setting.Title = textReader.ReadString();
        textReader.Read();
        setting.Description = textReader.ReadString();
        textReader.Read();
        setting.IsWinner = Convert.ToBoolean(textReader.ReadString());
        _items.Add(setting);
      }
    }
  catch (Exception ex)
  {
    throw new ApplicationException("Unable to open Settings file. " + ex.Message);
  }
  finally
  {
    reader.Close();
  }
}

public void WriteXml(System.Xml.XmlWriter writer)
{
  XmlTextWriter textWriter = writer as XmlTextWriter;
  textWriter.Formatting = Formatting.Indented;
  textWriter.WriteStartDocument();
  textWriter.WriteStartElement("Settings");

  foreach (Setting setting in _items)
  {
    string color = TypeDescriptor.GetConverter(typeof(Color)).ConvertToString(setting.Color);

    textWriter.WriteStartElement("Setting");

    textWriter.WriteElementString("Percentage", setting.Percentage.ToString());
    textWriter.WriteElementString("Color", color);
    textWriter.WriteElementString("Title", setting.Title);
    textWriter.WriteElementString("Description", setting.Description);
    textWriter.WriteElementString("IsWinner", setting.IsWinner.ToString());

    textWriter.WriteEndElement(); // close Setting tag
  }

  textWriter.WriteEndElement(); // close Settings tag
  textWriter.WriteEndDocument();
  textWriter.Close();
}

#endregion

#region IEnumerable Members

  public IEnumerator GetEnumerator()
  {
    return _items.GetEnumerator();
  }

#endregion
 }
}

ReadXML() casts XMLReader to a XMLTextReader to consume the WhiteSpaceHandling property.  Likewise, WriteXML casts XMLWriter to XMLTextWriter to use the Formatting property.   The structure used here of Settings/Setting/<some properties> is entirely arbitrary.  You can read and write to and from any xml structure you care to put together.

 | 
Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, i, strike, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview