I have been having problem with deseriailizing XML code genereated by XmlSerializer when child classes are used. The UnknownAttribute and UnknownNode events triggers and I assumed there was an error deserializing the XML. Turns out that not setting up handlers or ignoring the events still let the deserialization succeed.
Example code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
namespace XmlTest
{
public class Program
{
public abstract class ParentClass
{
public int Prop1 { get; set; } = 1;
public int Prop2 { get; set; } = 2;
}
public class ChildClassA : ParentClass
{
public int PropA3 { get; set; } = 3;
public int PropA4 { get; set; } = 4;
}
public class ChildClassB : ParentClass
{
public int PropB5 { get; set; } = 5;
public int PropB6 { get; set; } = 6;
}
[XmlInclude(typeof(ChildClassA))]
[XmlInclude(typeof(ChildClassB))]
public class CollectionClass
{
public List<ParentClass> classList { get; set; }
}
static void Main(string[] args)
{
CollectionClass collectionClassWrite = new CollectionClass();
collectionClassWrite.classList = new List<ParentClass>();
collectionClassWrite.classList.Add(new ChildClassA());
collectionClassWrite.classList.Add(new ChildClassB());
// Serialize the class to a file
string filePath = Path.GetTempFileName() + ".xml";
XmlSerializer serializer = new XmlSerializer(typeof(CollectionClass));
TextWriter writer = new StreamWriter(filePath);
serializer.Serialize(writer, collectionClassWrite);
writer.Close();
XmlSerializer deserializer = new XmlSerializer(typeof(CollectionClass));
// Event handlers for unknown nodes and attributes -
serializer.UnknownNode += new
XmlNodeEventHandler(serializer_UnknownNode);
serializer.UnknownAttribute += new
XmlAttributeEventHandler(serializer_UnknownAttribute);
// Deserialize the class from the file
FileStream fs = new FileStream(filePath, FileMode.Open);
CollectionClass classCollectionOpen = (CollectionClass)serializer.Deserialize(fs);
fs.Close();
Console.ReadLine();
}
private static void serializer_UnknownAttribute(object sender, XmlAttributeEventArgs e)
{
Console.WriteLine($"Unknown Attribute at {e.LineNumber}:{e.LinePosition}");
}
private static void serializer_UnknownNode(object sender, XmlNodeEventArgs e)
{
Console.WriteLine($"Unknown Node at {e.LineNumber}:{e.LinePosition}");
}
}
}
The generated XML looks like this:
<?xml version="1.0" encoding="utf-8"?>
<CollectionClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<classList>
<ParentClass xsi:type="ChildClassA">
<Prop1>1</Prop1>
<Prop2>2</Prop2>
<PropA3>3</PropA3>
<PropA4>4</PropA4>
</ParentClass>
<ParentClass xsi:type="ChildClassB">
<Prop1>1</Prop1>
<Prop2>2</Prop2>
<PropB5>5</PropB5>
<PropB6>6</PropB6>
</ParentClass>
</classList>
</CollectionClass>
The UnknownAttribute and UnknownNode events gets triggered at (line:colum) 4:28 and 10:28, the xsi:type="ChildClassA" and xsi:type="ChildClassB" attributes.
It appears that these events have to be ignored for the code to work. My question then becomes: Why are the events raised when XmlSerializer clearly knows how to handle the XML code?
So the XmlSerializer does not know what will be serialed as a ParentClass.
The solution is to make it aware of possible types.
The issue is that Array needs to know what type will be present insead of the whole class.
public record CollectionClass
{
[XmlArrayItem("ChildClassA", typeof(ChildClassA))]
[XmlArrayItem("ChildClassB", typeof(ChildClassB))]
public List<ParentClass> ClassList { get; set; } = new();
}