conciliator
conciliator

Reputation: 6138

How can I control the name of an XML element when using custom serialization in C#?

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

Answers (3)

Sean Skelly
Sean Skelly

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

Rich
Rich

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

Alexander Petrov
Alexander Petrov

Reputation: 14231

There are two ways to do it.

  1. Add XmlRoot attribute to Company class definition:

     [XmlRoot("Manufacturer")]
     public class Company
    
  2. Add XmlRootAttribute to XmlSerializer constructor.

     manufacturerSerializer = new XmlSerializer(Manufacturer.GetType(),
         new XmlRootAttribute("Manufacturer"));
    

    Pay attention to this: Dynamically Generated Assemblies.

Upvotes: 1

Related Questions