将XML元素序列化为其类名

时间:2013-04-24 16:59:37

标签: c# xml xml-serialization ixmlserializable

简而言之,我希望从一组对象中创建一个XML模式,如下所示;

<?xml version="1.0" encoding="utf-16"?>
<QBXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <QBXMLMsgsRq>
    <InvoiceQueryRq>
      <TxnID>1</TxnID>
    </InvoiceQueryRq>
    <InvoiceAddRq>
      <TxnID>2</TxnID>
    </InvoiceAddRq>
  </QBXMLMsgsRq>
</QBXML>

但我得到的是;

<?xml version="1.0" encoding="utf-16"?>
<QBXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <QBXMLMsgsRq>
    <Requests>
      <AbstractXmlSerializerOfQBBaseMessageRequest>
        <InvoiceQueryRq>
         <TxnID>1</TxnID>
        </InvoiceQueryRq>
        <InvoiceAddRq>
          <TxnID>2</TxnID>
       </InvoiceAddRq>
      </AbstractXmlSerializerOfQBBaseMessageRequest>
    </Requests>
  </QBXMLMsgsRq>
</QBXML>

QBXMLMsgsRq实际上是abstract class的集合,因为在不同类型的集合中可能有很多请求(InvoiceQueryRq在这里,但也可能有InvoiceAddRq,InvoiceDeleteRq等)。默认情况下,XML序列化程序不允许这样做,但经过一些研究后我发现了这个链接; XML Serialize generic list of serializable objects

我将AbstractXmlSerializer调整到

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

public class AbstractXmlSerializer<AbstractType> : IXmlSerializable
{
    public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o)
    {
        return o.Data;
    }

    public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o)
    {
        return o == null ? null : new AbstractXmlSerializer<AbstractType>(o);
    }

    private AbstractType _data;

    public AbstractType Data
    {
        get { return _data; }
        set { _data = value; }
    }

    /// <summary>
    /// **DO NOT USE** This is only added to enable XML Serialization.
    /// </summary>
    /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
    public AbstractXmlSerializer()
    {
        // Default Ctor (Required for Xml Serialization - DO NOT USE)
    }

    public AbstractXmlSerializer(AbstractType data)
    {
        _data = data;
    }

    #region IXmlSerializable Members
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null; // this is fine as schema is unknown.
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        // Cast the Data back from the Abstract Type.
        string typeAttrib = reader.LocalName; // reader.GetAttribute("type");

        // Ensure the Type was Specified
        if (typeAttrib == null)
            throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                "' because no 'type' attribute was specified in the XML.");

        Type type = Type.GetType(typeAttrib);

        // Check the Type is Found.
        if (type == null)
            throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                "' because the type specified in the XML was not found.");

        // Check the Type is a Subclass of the AbstractType.
        if (!type.IsSubclassOf(typeof(AbstractType)))
            throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                "' because the Type specified in the XML differs ('" + type.Name + "').");

        // Read the Data, Deserializing based on the (now known) concrete type.
        reader.ReadStartElement();
        this.Data = (AbstractType)new
            XmlSerializer(type).Deserialize(reader);
        reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        // Write the Type Name to the XML Element as an Attrib and Serialize
        Type type = _data.GetType();

        // BugFix: Assembly must be FQN since Types can/are external to current.
        new XmlSerializer(type).Serialize(writer, _data);
    }
    #endregion
}

为了方便和测试任何帮助的人,我正在处理的对象是;

[Serializable]
public class QBXML
{
    [XmlElement("QBXMLMsgsRq")]
    public QBXMLMsgsRq MessageRequests { get; set; }
}

[Serializable]
public class QBXMLMsgsRq
{
    public QBXMLMsgsRq()
        : base() 
    {
        Requests = new List<QBBaseMessageRequest>();
    }

    [XmlArray(""), XmlArrayItem("", Type = typeof(AbstractXmlSerializer<QBBaseMessageRequest>))]
    public List<QBBaseMessageRequest> Requests { get; set; }
}

public abstract class QBBaseMessageRequest
{
    [DefaultValue(""), XmlAttribute("requestID")]
    public string RequestID { get; set; }
}

[Serializable]
public class InvoiceQueryRq : QBBaseMessageRequest
{
    [DefaultValue(0), XmlElement("TxnID")]
    public int TransactionID { get; set; }
}

[Serializable]
public class InvoiceAddRq : QBBaseMessageRequest
{
    [DefaultValue(0), XmlElement("TxnID")]
    public int TransactionID { get; set; }
}

1 个答案:

答案 0 :(得分:3)

我认为只需标准的XML序列化属性,您就可以完成所需的一切,而无需自定义序列化程序。请参阅以下示例:

public class Container
{
    [XmlElement("ElementType1", typeof(ElementType1))]
    [XmlElement("ElementType2", typeof(ElementType2))]
    public ElementBase[] Elements { get; set; }
}

[XmlInclude(typeof(ElementType1)),XmlInclude(typeof(ElementType2))]
public abstract class ElementBase
{
    public string Name { get; set; }
}

public class ElementType1 : ElementBase
{
    public int ID1 { get; set; }
}

public class ElementType2 : ElementBase
{
    public int ID2 { get; set; }
}

使用默认序列化程序序列化一些测试数据......

var container = new Container
{
    Elements = new ElementBase[] {
        new ElementType1 { Name = "first object", ID1 = 999 },
        new ElementType2 { Name = "second object", ID2 = 31337 }
    }
};
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(Container));
serializer.Serialize(stream, container);

...您将获得以下输出,它看起来像您需要的格式:

<?xml version="1.0" encoding="utf-8"?>
<Container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <ElementType1>
        <Name>first object</Name>
        <ID1>999</ID1>
    </ElementType1>
    <ElementType2>
        <Name>second object</Name>
        <ID2>31337</ID2>
    </ElementType2>
</Container>