views:

269

answers:

2

I'm trying to serialize a class that inherits from a base class that implements IXmlSerializable.

The base class, called PropertyBag is a class that allows dynamic properties (credits to Marc Gravell).

I implemented IXmlSerializable so that the dynamic properties (stored in a Dictionary) are written as normal xml elements.

e.g. When serializing a class Person with a public property (non dynamic) Name and a dynamic property Age, I would like for it to generate the following XML:

<Person>
  <Name>Tim</Name>
  <DynamicProperties>
    <Country>
      <string>USA</string>
    </Country>
  </DynamicProperties>
<Person>

I can get the part to work with the following implementation of WriteXml in the base PropertyBag class:

public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteStartElement("DynamicProperties");

        // serialize every dynamic property and add it to the parent writer
        foreach (KeyValuePair<string, object> kvp in properties)
        {
            writer.WriteStartElement(kvp.Key);

            StringBuilder itemXml = new StringBuilder();
            using (XmlWriter itemWriter = XmlWriter.Create(itemXml))
            {
                // serialize the item
                XmlSerializer xmlSer = new XmlSerializer(kvp.Value.GetType());
                xmlSer.Serialize(itemWriter, kvp.Value);                    

                // read in the serialized xml
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(itemXml.ToString());

                // write to modified content to the parent writer
                writer.WriteRaw(doc.DocumentElement.OuterXml);
            }

            writer.WriteEndElement();
        }

        writer.WriteEndElement();
    }

However, when serializing the Person class, it no longer serializes the normal (non dynamic) properties unless I overwrite the WriteXml method in Person (which I do not want to do). Is there any way that in the base class I can automatically add the static properties? I know I can do this manually using reflection, but I was wondering if there is some built-in functionality in the .Net Framework?

+2  A: 

I've spent quite a bit of time with XmlSerializer (and various other serialization APIs), and I'm pretty sure that simply: you can't. Implementing IXmlSerializable is all or nothing.

The closest I can think of is to cheat and move all the fixed properties to a sub-object; this would give you slightly different xml - something like:

<FixedProperties>
   <Name>Tim</Name>
</FixedProperties> 
<DynamicProperties>
  <Country>
    <string>USA</string>
  </Country>
</DynamicProperties>

but I expect it would work. You would have pass-thru properties on your base object:

[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public FixedProperties FixedProps {get;set;}
public string Name {
    get {return FixedProps.Name;}
    set {FixedProps.Name = value;}
}

Make sense? You could also mark Name as [XmlIgnore], but it seems pretty redundant. In your bespoke serialize method you'd use new XmlSerializer(typeof(FixedProperties))

Edit: Here's a working "serialize" example:

using System;
using System.ComponentModel;
using System.Xml.Serialization;

static class Program
{
    static void Main()
    {
        MyType obj = new MyType { Name = "Fred" };
        var ser = new XmlSerializer(obj.GetType());
        ser.Serialize(Console.Out, obj);
    }
}
public class MyType : IXmlSerializable
{
    public MyType()
    {
        FixedProperties = new MyTypeFixedProperties();
    }
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public MyTypeFixedProperties FixedProperties { get; set; }
    [XmlIgnore]
    public string Name
    {
        get { return FixedProperties.Name; }
        set { FixedProperties.Name = value; }
    }

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

    void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
    {
        throw new System.NotImplementedException();
    }

    void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteStartElement("DynamicProperties");
        writer.WriteElementString("Foo", "Bar");
        writer.WriteEndElement();
        fixedPropsSerializer.Serialize(writer, FixedProperties);
    }
    static readonly XmlSerializer fixedPropsSerializer
        = new XmlSerializer(typeof(MyTypeFixedProperties));

}
[XmlRoot("FixedProperties")]
public class MyTypeFixedProperties
{
    public string Name { get; set; }
}
Marc Gravell
Right on the money. That's why the DataContract stuff was added in the newer versions of .Net. This is why "old" asmx web services blow too, because you can't detect the serialization of your objects without completely writing the serialize from scratch.
jvenema
That should work, but not for my intended purpose. The derived classes already exist, I just wanted to add functionality for dynamic properties to them. The fixed properties should remain in the derived class. I greatly appreciate the input though!
Tim Weckx
@jvenema - Would this be possible using DataContract attributes then and if so, do you have any good pointers for me?
Tim Weckx
A: 

Marc, your answer on putting the FixedProperties in a seperate collection got me thinking that instead of inheriting from PropertyBag, I should create a property of that type.

So I created a PropertyBagWrapper class that my Person class inherits from and it works.

[Serializable]
[TypeDescriptionProvider(typeof(PropertyBagDescriptionProvider))]    
public abstract class PropertyBagWrapper
{
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]    
    public PropertyBag DynamicProperties { get; set; }

    public object this[string name]
    {
        get { return DynamicProperties[name]; }
        set { DynamicProperties[name] = value; }
    }
    protected PropertyBagWrapper()
    {
        DynamicProperties = new PropertyBag(this.GetType());
    }
}

[Serializable]    
public class Person : PropertyBagWrapper
{
    [Browsable(true)]
    public string Name { get; set; }
}

I won't repeat all the code for the PropertyBag and the custom classes needed for ICustomTypeDescriptor implementation, you can find that here.

I did move the TypeDescriptionProvider attribute from the PropertyBag class to the PropertyBagWrapper class.

The PropertyBag class still has the same implementation for WriteXml() method as posted in the question.

Tim Weckx