سؤال

In a nutshell, I'm looking to create an XML schema from a set of objects that looks like the following;

<?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>

but what I'm getting is;

<?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>

The QBXMLMsgsRq is actually a collection of an abstract class as there may be many requests in the collection of different types (InvoiceQueryRq here but there could also be InvoiceAddRq, InvoiceDeleteRq, etc). By default the XML serializer doesn't allow this but after doing a bit of research I found this link; XML Serialize generic list of serializable objects

I tweaked the AbstractXmlSerializer there to

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
}

For convenience and testing for anyone helping, the objects I'm dealing with are;

[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; }
}
هل كانت مفيدة؟

المحلول

I think you can do everything you want with just the standard XML serialization attributes, without a custom serializer. See the example below:

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; }
}

Serializing some test data using the default serializer...

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);

... and you get the following output, which looks like the format you need:

<?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>
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top