How to revert back to 'default' XML serialization when implementing IXmlSerializable in a base class?
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 P开发者_如何学JAVAerson 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?
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, 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.
精彩评论