Reputation: 6138
I'm working with an externally developed XSD that I need to conform to, where a field Engine
can be one of two types: Electric
or Combustion
. Both are derived from super-class Engine
.
There are multiple ways to have the XmlSerializer
in C# handle this, but all are seemingly associated with the same side-effect: default serialization naming conventions are tossed out the window when using a custom serializer-apporach. I want to mimic the default behaviour for the remaining fields of the class Vehicle
.
Consider the following:
using System;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace DMV
{
public class Vehicle : IXmlSerializable
{
public Engine Engine { get; set; }
[XmlElement("Manufacturer")]
public Company Manufacturer { get; set; }
public void WriteXml(XmlWriter writer)
{
XmlSerializer engineSerializer = new XmlSerializer(Engine.GetType());
engineSerializer.Serialize(writer, Engine);
XmlSerializer manufacturerSerializer = new XmlSerializer(Manufacturer.GetType());
manufacturerSerializer.Serialize(writer, Manufacturer);
}
}
public abstract class Engine { }
public class Electric : Engine
{
public int Poles { get; set; }
}
public class Combustion : Engine
{
public int CC { get; set; }
}
public class Company
{
public string Name { get; set; }
}
}
I try to run this using following serialization code:
Vehicle vehicle = new Vehicle();
vehicle.Engine = new Electric();
vehicle.Manufacturer = new Company();
XmlSerializer serializer = new XmlSerializer(typeof(Vehicle));
TextWriter writer = new StringWriter();
serializer.Serialize(writer, vehicle);
Console.WriteLine(writer.ToString());
writer.Close();
The result is the following dissapointing pile of caballus faeces:
<Vehicle>
<Electric>
<Poles>0</Poles>
</Electric>
<Company/>
</Vehicle>
(Yes, the Engine
field is serialized properly, but Company
not so much.)
If I had used the default (built-in) serialization I would've gotten the following (desired!) output:
<Vehicle>
<Manufacturer />
</Vehicle>
How can I both serialize the derived classes Electric
or Combustion
, while retaining the default naming convention for field Manufacturer
(of type Company
)?
Upvotes: 3
Views: 616
Reputation: 1324
Try using a specific constructor for XmlSerializer
that accepts an array of 'other types' to handle your derived classes, rather than implementing IXmlSerializable
and doing the serialization manually. The serializer will now handle your derived classes, but also pick up your other attributes (such as XmlElement
) the way you want:
using System;
using System.IO;
using System.Xml.Serialization;
namespace SomeNamespace
{
public class Program
{
static void Main()
{
Vehicle vehicle = new Vehicle();
vehicle.Engine = new Electric();
vehicle.Manufacturer = new Company();
XmlSerializer serializer = new XmlSerializer(typeof(Vehicle), new Type[] { typeof(Combustion), typeof(Electric)});
TextWriter writer = new StringWriter();
serializer.Serialize(writer, vehicle);
Console.WriteLine(writer.ToString());
writer.Close();
}
}
public class Vehicle
{
public Engine Engine { get; set; }
[XmlElement("Manufacturer")]
public Company Manufacturer { get; set; }
}
public abstract class Engine { }
public class Electric : Engine
{
public int Poles { get; set; }
}
public class Combustion : Engine
{
public int CC { get; set; }
}
public class Company
{
public string Name { get; set; }
}
}
Outputs:
<?xml version="1.0" encoding="utf-16"?>
<Vehicle xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Engine xsi:type="Electric">
<Poles>0</Poles>
</Engine>
<Manufacturer />
</Vehicle>
Unlike the accepted answer, this approach has the advantage of not linking the base class to all possible derived classes. (Any time you add a new derived type in that approach, you have to come back and edit the base type.) While this example has Program.Main()
hard-coding the array of derived types, you could just as easily use Reflection to build that array, making it even easier to add new derived types.
Upvotes: 2
Reputation: 271
You can get rid of the custom serializer by doing this:
public class Vehicle
{
[XmlElement(typeof(Electric), ElementName = "Electric")]
[XmlElement(typeof(Combustion), ElementName = "Combustion")]
public Engine Engine { get; set; }
[XmlElement("Manufacturer")]
public Company Manufacturer { get; set; }
}
Upvotes: 2
Reputation: 14231
There are two ways to do it.
Add XmlRoot
attribute to Company
class definition:
[XmlRoot("Manufacturer")]
public class Company
Add XmlRootAttribute
to XmlSerializer
constructor.
manufacturerSerializer = new XmlSerializer(Manufacturer.GetType(),
new XmlRootAttribute("Manufacturer"));
Pay attention to this: Dynamically Generated Assemblies.
Upvotes: 1