Сериализация элементов XML в качестве имени класса
Короче говоря, я хочу создать схему 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 общий список сериализуемых объектов
Я подправил 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 ответ
Я думаю, что вы можете делать все, что хотите, только с помощью стандартных атрибутов сериализации 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>