How to serialize a list of an interface?
Unfortunately it's not supported out of the box, but it's doable. You would have to use the IXmlSerializable
interface and write your custom serialization. It shouldn't be that much difficult - you'd need to enumerate through your list, get the underlying type of each object and create a new XmlSerializer
for this type. Deserialization could be a bit more tricky, as you'd need to parse the class names to determine runtime types.
XML Serialize generic list of serializable objects
I have an solution for a generic List<> with dynamic binded items.
class PersonalList it's the root element
[XmlRoot("PersonenListe")]
[XmlInclude(typeof(Person))] // include type class Person
public class PersonalList
{
[XmlArray("PersonenArray")]
[XmlArrayItem("PersonObjekt")]
public List<Person> Persons = new List<Person>();
[XmlElement("Listname")]
public string Listname { get; set; }
// Konstruktoren
public PersonalList() { }
public PersonalList(string name)
{
this.Listname = name;
}
public void AddPerson(Person person)
{
Persons.Add(person);
}
}
class Person it's an single list element
[XmlType("Person")] // define Type
[XmlInclude(typeof(SpecialPerson)), XmlInclude(typeof(SuperPerson))]
// include type class SpecialPerson and class SuperPerson
public class Person
{
[XmlAttribute("PersID", DataType = "string")]
public string ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("City")]
public string City { get; set; }
[XmlElement("Age")]
public int Age { get; set; }
// Konstruktoren
public Person() { }
public Person(string name, string city, int age, string id)
{
this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
}
}
class SpecialPerson inherits Person
[XmlType("SpecialPerson")] // define Type
public class SpecialPerson : Person
{
[XmlElement("SpecialInterests")]
public string Interests { get; set; }
public SpecialPerson() { }
public SpecialPerson(string name, string city, int age, string id, string interests)
{
this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
this.Interests = interests;
}
}
class SuperPerson inherits Person
[XmlType("SuperPerson")] // define Type
public class SuperPerson : Person
{
[XmlArray("Skills")]
[XmlArrayItem("Skill")]
public List<String> Skills { get; set; }
[XmlElement("Alias")]
public string Alias { get; set; }
public SuperPerson()
{
Skills = new List<String>();
}
public SuperPerson(string name, string city, int age, string id, string[] skills, string alias)
{
Skills = new List<String>();
this.Name = name;
this.City = city;
this.Age = age;
this.ID = id;
foreach (string item in skills)
{
this.Skills.Add(item);
}
this.Alias = alias;
}
}
and the main test Source
static void Main(string[] args)
{
PersonalList personen = new PersonalList();
personen.Listname = "Friends";
// normal person
Person normPerson = new Person();
normPerson.ID = "0";
normPerson.Name = "Max Man";
normPerson.City = "Capitol City";
normPerson.Age = 33;
// special person
SpecialPerson specPerson = new SpecialPerson();
specPerson.ID = "1";
specPerson.Name = "Albert Einstein";
specPerson.City = "Ulm";
specPerson.Age = 36;
specPerson.Interests = "Physics";
// super person
SuperPerson supPerson = new SuperPerson();
supPerson.ID = "2";
supPerson.Name = "Superman";
supPerson.Alias = "Clark Kent";
supPerson.City = "Metropolis";
supPerson.Age = int.MaxValue;
supPerson.Skills.Add("fly");
supPerson.Skills.Add("strong");
// Add Persons
personen.AddPerson(normPerson);
personen.AddPerson(specPerson);
personen.AddPerson(supPerson);
// Serialize
Type[] personTypes = { typeof(Person), typeof(SpecialPerson), typeof(SuperPerson) };
XmlSerializer serializer = new XmlSerializer(typeof(PersonalList), personTypes);
FileStream fs = new FileStream("Personenliste.xml", FileMode.Create);
serializer.Serialize(fs, personen);
fs.Close();
personen = null;
// Deserialize
fs = new FileStream("Personenliste.xml", FileMode.Open);
personen = (PersonalList)serializer.Deserialize(fs);
serializer.Serialize(Console.Out, personen);
Console.ReadLine();
}
Important is the definition and includes of the diffrent types.
Serialize list of interfaces using XML Serialization
You will need to replace [XmlElement(ElementName = "Meters")]
with [XmlArray(ElementName = "Meters")]
as the error states:
{"XmlElement, XmlText, and XmlAnyElement cannot be used in conjunction with XmlAttribute, XmlAnyAttribute, XmlArray, or XmlArrayItem."}
To define an Array for the XmlSerializer
you can mark it with XmlArray
and its corresponding items with XmlArrayItem
Example:
public class MeterWalkOrder
{
public MeterWalkOrder()
{
Meters = new List<IMeter>();
}
public String Name { get; set; }
[XmlIgnore]
public List<IMeter> Meters { get; set; }
[XmlArrayItem(ElementName = "Meter")]
[XmlArray(ElementName = "Meters")]
public List<Meter> SerializableMeters
{
get
{
return Meters.Cast<Meter>().ToList();
}
set
{
Meters = new List<IMeter>(value);
}
}
}
public interface IMeter {
int MeterID { get; set; }
}
public class Meter : IMeter {
public int MeterID { get; set; }
public string SerialNumber { get; set; }
}
Test:
var data = new MeterWalkOrder{ Name = "Red Route", Meters = new List<IMeter>
{
new Meter{ MeterID = 1, SerialNumber = "12345"},
new Meter{ MeterID = 2, SerialNumber = "SE"}
}};
using (var stream = new FileStream("C:\\Users\\Adam\\Desktop\\Test2.xml", FileMode.OpenOrCreate))
{
XmlSerializer ser = new XmlSerializer(typeof(MeterWalkOrder));
ser.Serialize(stream, data);
}
Result:
<?xml version="1.0"?>
<MeterWalkOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Red Route</Name>
<Meters>
<Meter>
<MeterID>1</MeterID>
<SerialNumber>12345</SerialNumber>
</Meter>
<Meter>
<MeterID>2</MeterID>
<SerialNumber>SE</SerialNumber>
</Meter>
</Meters>
</MeterWalkOrder>
XML serialization of interface property
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:
Hide it and deal with it in another property
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...
Implement IXmlSerializable
Similar to the first option in that you take full control of things but
- Pros
- You don't have nasty 'fake' properties hanging around.
- you can interact directly with the xml structure adding flexibility/versioning
- Cons
- you may end up having to re-implement the wheel for all the other properties on the class
Issues of duplication of effort are similar to the first.
Modify your property to use a wrapping type
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.
XML Serialization of an Interface
Interfaces seems to be cumbersome for serialization/deserialization processes. You might need to add another public member to the class that uses a concrete type and mark the interface property as xml ignore. This way you can deserialize the object without loosing your contract base.
Something like the following:
[Serializable]
public class Prototype
{
public virtual long Id { get; private set; }
public virtual string Name { get; set; }
[XMLIgnore]
public virtual IList<AttributeGroup> AttributeGroups {
get { return this.AttributeGroupsList; }
}
public virtual List<AttributeGroup> AttributeGroupsList { get; private set;}
}
For more information about deserialization attributes please check XmlAttributes Properties.
Regards,
Related Topics
How to Write to a Onenote 2013 Page Using C# and the Onenote Interop
Return Only Digits 0-9 from a String
An Elegant Way to Consume (All Bytes of A) Binaryreader
The Data Source Does Not Support Server-Side Data Paging
Async Await VS Getawaiter().Getresult() and Callback
How to Check If Ioexception Is Not-Enough-Disk-Space-Exception Type
Can You Prevent Your ASP.NET Application from Shutting Down
Circular Reference Causing Stack Overflow with Automapper
How to Change Listbox Selection Background Color
Scaling a System.Drawing.Bitmap to a Given Size While Maintaining Aspect Ratio
When Using .Net MVC Radiobuttonfor(), How to Group So Only One Selection Can Be Made
Why Does Random.Next() Always Return the Same Number
How to Figure Out Which Key of Modelstate Has Error
Unity3D Ui, Calculation for Position Dragging an Item
How to Center Your Main Window in Wpf
Why Do I Get "System.Data.Datarowview" Instead of Real Values in My Winforms Listbox