简体   繁体   中英

C# XML Serializing with Abstract Class

I'm currently trying to set up some commands as classes for serializing for a communication protocol. My code is basically as follows:

[XmlRoot("Message")]
[Serializable]
public class Message
{
    private Command[] _commands;

    [XmlAttribute("ver")]
    public int Version { get; set; }
    [XmlAttribute("msid")]
    public Guid Id { get; set; }

    [XmlArray("Commands")]
    [XmlArrayItem(typeof(HealthCheckCommand))]
    [XmlArrayItem(typeof(TestCommand))]
    public Command[] Commands
    {
        get { return _commands; }
        set { _commands = value; }
    }
}

public enum CommandTypes
{
    healthcheck
}

[XmlType(TypeName = "Command")]
public abstract class Command
{
    String CommandType { get; set; }
}

public class HealthCheckCommand : Command
{
    [XmlAttribute("type")]
    public string CommandType
    {
        get { return "healthcheck"; }
        set { throw new NotImplementedException(); }
    }
}

public class TestCommand : Command
{
    [XmlAttribute("type")]
    public string CommandType
    {
        get { return "test"; }
        set { throw new NotImplementedException(); }
    }
}

What I need to get from this is:

<Message ver="1" msid="00000000-0000-0000-0000-000000000000">
  <Commands>
    <Command type="healthcheck" />
    <Command type="test" />
  </Commands>
</Message>

What I'm getting is:

<Message ver="1" msid="00000000-0000-0000-0000-000000000000">
  <Commands>
    <HealthCheckCommand type="healthcheck" />
    <TestCommand type="test" />
  </Commands>
</CTM>

When I try to override the XmlArrayItem name with the same name, it of course throws an error. If I use a list, then it works, but I get all the namespace stuff inside of the subtypes, which I don't want. I could go in and remove those namespace items after the fact, but I'd rather not. There must be a way to do this.

Thanks for the help!

EDIT:

Here's the serialization code:

XmlSerializer serializer = new XmlSerializer(typeof (Message));
        using (TextWriter writer = new StreamWriter(@"C:\Xml.txt"))
        {
            XmlSerializerNamespaces xmlSerializerNamespaces = new XmlSerializerNamespaces();
            xmlSerializerNamespaces.Add("", "");
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.OmitXmlDeclaration = true;

            using (
            XmlWriter xmlwriter = XmlWriter.Create(writer, new XmlWriterSettings { OmitXmlDeclaration = true }))
            {
                serializer.Serialize(xmlwriter, message, xmlSerializerNamespaces);
            }
        }
    }

Include the derived types by adding the relevant XmlInclude attributes onto Message :

[XmlInclude(typeof(HealthCheckCommand))]
[XmlInclude(typeof(TestCommand))]

Then specify the element name for your Command[] array items:

[XmlArrayItem("Command")]

This creates XML like this, which is probably the same as when you used a List :

<Message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ver="0" msid="00000000-0000-0000-0000-000000000000">
    <Commands>
        <Command xsi:type="HealthCheckCommand" type="healthcheck" />
        <Command xsi:type="TestCommand" type="test" />
    </Commands>
</Message>

Unfortunately the xsi:type attributes are required in order for deserialisation to work (else how would the serialiser know which types to use?). These can easily be removed after the fact, though. Parse the XML with XDocument and remove them like so:

XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";

doc.Descendants()
   .Attributes(xsi + "type")
   .Remove();

doc.Descendants()
   .Attributes()
   .Where(a => a.IsNamespaceDeclaration && a.Value == xsi)
   .Remove();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM