XML Serialization Issues or Having Fun with TimeSpan and XmlIgnore

November 10th 2010 Serialization .NET Framework

Recently I tackled a seemingly simple task: XML serialization of a generic class used with a TimeSpan data type. Basically I had a situation similar to the following two classes:

// the generic class used
public class GenericClass<T> where T: new()
{
    public virtual T Value { get; set; }

    public GenericClass()
    {
        Value = new T();
    }
}

// the class for XML serialization
public class Configuration
{
    public GenericClass<TimeSpan> Time { get; set; }

    public Configuration()
    {
        Time = new GenericClass<TimeSpan>();
    }
}

The only remaining thing to do should be calling the XmlSerializer.Serialize method:

Configuration config = new Configuration();
XmlSerializer serializer = new XmlSerializer(typeof(Configuration));
using (FileStream stream = new FileStream("configuration.xml", FileMode.Create))
{
    serializer.Serialize(stream, config);
}

Though, the result wasn't what I expected:

<?xml version="1.0"?>
<Configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Time>
    <Value />
  </Time>
</Configuration>

Where did the value go? After some exploration it turned out that XmlSerializer doesn't serialize the TimeSpan structure at all. Unfortunately the suggested standard workaround by hiding the property with XmlIgnore attribute and adding an additional property of the desired type can't be used in the above scenario because it has to be applied to the generic class only when it is used with TimeSpan data type. The problem also can't be solved by deriving a class from TimeSpan and fixing its XML serialization because it is a structure which doesn't support inheritance. Well, it looked like I'll just have to derive a new class from GenericClass for the case of TimeSpan:

public class TimeSpanClass : GenericClass<TimeSpan>
{
    [XmlIgnore]
    public override TimeSpan Value
    {
        get
        {
            return base.Value;
        }
        set
        {
            base.Value = value;
        }
    }

    [XmlElement(ElementName="Value")]
    public string StringValue
    {
        get
        {
            return Value.ToString(@"hh\:mm\:ss");
        }
        set
        {
            Value = TimeSpan.ParseExact(value, 
                @"hh\:mm\:ss", CultureInfo.InvariantCulture);
        }
    }
}

This should work, right? Wrong. The following exception is thrown when initializing XmlSerializer for the modified Configuration class now containing TimeSpanClass instead of GenericClass<TimeSpan>:

The XML element 'Value' from namespace '' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.

How come? Well the XmlIgnore attribute doesn't have any effect when used in a derived class on an overridden property. If you remove the XmlElement attribute from the StringValue property, you'll see that both properties will get serialized:

<?xml version="1.0"?>
<Configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Time>
    <Value />
    <StringValue>00:00:00</StringValue>
  </Time>
</Configuration>

So, is there really no way to get this working? There is, but only if you do your own serialization by implementing IXmlSerializable interface on the TimeSpanClass:

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

public void ReadXml(System.Xml.XmlReader reader)
{
    reader.ReadStartElement();

    Value = TimeSpan.ParseExact(
        reader.ReadElementContentAsString("Value", String.Empty),
        @"hh\:mm\:ss",
        CultureInfo.InvariantCulture);

    reader.ReadEndElement();
}

public void WriteXml(System.Xml.XmlWriter writer)
{
    writer.WriteElementString("Value", Value.ToString(@"hh\:mm\:ss"));
}

This will finally produce the desired serialization:

<?xml version="1.0"?>
<Configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Time>
    <Value>00:00:00</Value>
  </Time>
</Configuration>

Writing your own XML serialization logic pretty much defeats the purpose of having XmlSerializer class available in the first place. It's not that difficult in this simple case but it can get quite tedious if you have to write it for a more complex class. It's definitely not something I'm looking forward to writing, but if it's the most elegant way to get the job done... Since the problem is already present in .NET framework for almost a decade, it's a pretty safe bet it won't get fixed any time soon if ever.

Copyright
Creative Commons License