I would like to XML serialize an object that has (among other) a property of type IModelObject (which is an interface).
public class Example { public IModelObject Model { get; set; } }
When I try to serialize an object of this class, I receive the following error:
"Cannot serialize member Example.Model of type Example because it is an interface."
I understand that the problem is that an interface cannot be serialized. However, the concrete Model object type is unknown until runtime.
Replacing the IModelObject interface with an abstract or concrete type and use inheritance with XMLInclude is possible, but seems like an ugly workaround.
Any suggestions?
XML serialization is the process of converting XML data from its representation in the XQuery and XPath data model, which is the hierarchical format it has in a Db2® database, to the serialized string format that it has in an application.
XML serialization serializes only the public fields and property values of an object into an XML stream. XML serialization does not include type information. For example, if you have a Book object that exists in the Library namespace, there is no guarantee that it is deserialized into an object of the same type.
XML serialization can also be used to serialize objects into XML streams that conform to the SOAP specification. SOAP is a protocol based on XML, designed specifically to transport procedure calls using XML. To serialize or deserialize objects, use the XmlSerializer class.
There are three types of serialization in . Net : Binary Serialization, SOAP Serialization and XML Serialization.
This is simply an inherent limitation of declarative serialization where type information is not embedded within the output.
On trying to convert <Flibble Foo="10" />
back into
public class Flibble { public object Foo { get; set; } }
How does the serializer know whether it should be an int, a string, a double (or something else)...
To make this work you have several options but if you truly don't know till runtime the easiest way to do this is likely to be using the XmlAttributeOverrides.
Sadly this will only work with base classes, not interfaces. The best you can do there is to ignore the property which isn't sufficient for your needs.
If you really must stay with interfaces you have three real options:
Ugly, unpleasant boiler plate and much repetition but most consumers of the class will not have to deal with the problem:
[XmlIgnore()] public object Foo { get; set; } [XmlElement("Foo")] [EditorVisibile(EditorVisibility.Advanced)] public string FooSerialized { get { /* code here to convert any type in Foo to string */ } set { /* code to parse out serialized value and make Foo an instance of the proper type*/ } }
This is likely to become a maintenance nightmare...
Similar to the first option in that you take full control of things but
Issues of duplication of effort are similar to the first.
public sealed class XmlAnything<T> : IXmlSerializable { public XmlAnything() {} public XmlAnything(T t) { this.Value = t;} public T Value {get; set;} public void WriteXml (XmlWriter writer) { if (Value == null) { writer.WriteAttributeString("type", "null"); return; } Type type = this.Value.GetType(); XmlSerializer serializer = new XmlSerializer(type); writer.WriteAttributeString("type", type.AssemblyQualifiedName); serializer.Serialize(writer, this.Value); } public void ReadXml(XmlReader reader) { if(!reader.HasAttributes) throw new FormatException("expected a type attribute!"); string type = reader.GetAttribute("type"); reader.Read(); // consume the value if (type == "null") return;// leave T at default value XmlSerializer serializer = new XmlSerializer(Type.GetType(type)); this.Value = (T)serializer.Deserialize(reader); reader.ReadEndElement(); } public XmlSchema GetSchema() { return(null); } }
Using this would involve something like (in project P):
public namespace P { public interface IFoo {} public class RealFoo : IFoo { public int X; } public class OtherFoo : IFoo { public double X; } public class Flibble { public XmlAnything<IFoo> Foo; } public static void Main(string[] args) { var x = new Flibble(); x.Foo = new XmlAnything<IFoo>(new RealFoo()); var s = new XmlSerializer(typeof(Flibble)); var sw = new StringWriter(); s.Serialize(sw, x); Console.WriteLine(sw); } }
which gives you:
<?xml version="1.0" encoding="utf-16"?> <MainClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <RealFoo> <X>0</X> </RealFoo> </Foo> </MainClass>
This is obviously more cumbersome for users of the class though avoids much boiler plate.
A happy medium may be merging the XmlAnything idea into the 'backing' property of the first technique. In this way most of the grunt work is done for you but consumers of the class suffer no impact beyond confusion with introspection.
The solution to this is using reflection with the DataContractSerializer. You don't even have to mark your class with [DataContract] or [DataMember]. It will serialize any object, regardless of whether it has interface type properties (including dictionaries) into xml. Here is a simple extension method that will serialize any object into XML even if it has interfaces (note you could tweak this to run recursively as well).
public static XElement ToXML(this object o) { Type t = o.GetType(); Type[] extraTypes = t.GetProperties() .Where(p => p.PropertyType.IsInterface) .Select(p => p.GetValue(o, null).GetType()) .ToArray(); DataContractSerializer serializer = new DataContractSerializer(t, extraTypes); StringWriter sw = new StringWriter(); XmlTextWriter xw = new XmlTextWriter(sw); serializer.WriteObject(xw, o); return XElement.Parse(sw.ToString()); }
what the LINQ expression does is it enumerates each property, returns each property that is an interface, gets the value of that property (the underlying object), gets the type of that concrete object puts it into an array, and adds that to the serializer's list of known types.
Now the serializer knows how about the types it is serializing so it can do its job.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With