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