The attribute "{http://www.w3.org/2001/XMLSchema-instance}type"
, which usually appears with the prefix "xsi:type
", is a standard way for a polymorphic element to explicitly assert its type. XmlSerializer
uses it to determine the type to deserialize -- and thus writes it during serialization for use later.
There's no trivial way to suppress output of the type while keeping the same element name for each polymorphic type. (It would be easy if you had a different element name for each subclass of AParty
, but you don't.) The best option would be for ManifestHeader
to implement IXmlSerializable
. You don't fully specify the ManifestHeader
, so consider the following example:
[XmlRoot("ManifestHeader", Namespace = ManifestHeader.XmlNamespace)]
public class ManifestHeader : IXmlSerializable
{
public const string XmlNamespace = "MyNamespace";
public static XmlSerializerNamespaces GetXmlSerializerNamespaces()
{
var ns = new XmlSerializerNamespaces();
ns.Add("", ManifestHeader.XmlNamespace);
return ns;
}
// Some example properties
public string AProperty { get; set; }
public string ZProperty { get; set; }
// The list of parties.
public AParty[] Parties { get; set; }
#region IXmlSerializable Members
XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
throw new NotImplementedException();
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
var ns = GetXmlSerializerNamespaces();
writer.WriteElementString("ZProperty", ZProperty);
foreach (var value in Parties)
{
XmlSerializationHelper.SerializeElementTo(value, "Party", ManifestHeader.XmlNamespace, writer, ns);
}
writer.WriteElementString("AProperty", AProperty);
}
#endregion
}
public abstract class AParty
{
[XmlAttribute]
public abstract string Role { get; set; } // Returns a constant string; setter does nothing.
}
This manually serializes the properties of ManifestHeader
(if any), loops through the elements of the Party
array and serializes each, replacing their element names with "Party"
.
It uses the following helper methods. Note that, if one is to override the root element name using the XmlSerializer(Type, XmlRootAttribute)
constructor, one must cache the serializer in a hash table to avoid a horrible memory leak:
public static class XmlSerializationHelper
{
public static void SerializeElementTo<T>(T value, string elementName, string elementNamespace, XmlWriter writer, XmlSerializerNamespaces ns)
{
var serializer = XmlSerializerRootAttributeCache.DemandSerializer(value.GetType(), elementName, elementNamespace);
serializer.Serialize(writer, value, ns);
}
public static string GetXml<T>(this T obj)
{
return GetXml(obj, false);
}
public static string GetXml<T>(this T obj, bool omitNamespace)
{
return GetXml(obj, new XmlSerializer(obj.GetType()), omitNamespace);
}
public static string GetXml<T>(this T obj, XmlSerializer serializer)
{
return GetXml(obj, serializer, false);
}
public static string GetXml<T>(T obj, XmlSerializer serializer, bool omitStandardNamespaces)
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
{
ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
}
return GetXml(obj, serializer, ns);
}
public static string GetXml<T>(T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
{
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
serializer.Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
public static string GetXml<T>(this T obj, XmlSerializerNamespaces ns)
{
return GetXml(obj, new XmlSerializer(obj.GetType()), ns);
}
}
public static class XmlSerializerRootAttributeCache
{
readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
readonly static object padlock = new object();
static XmlSerializerRootAttributeCache()
{
cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
}
static XmlSerializer CreateSerializer(Type rootType, string rootName, string rootNamespace)
{
return new XmlSerializer(rootType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
}
public static XmlSerializer DemandSerializer(Type rootType, string rootName, string rootNamespace)
{
var key = Tuple.Create(rootType, rootName, rootNamespace);
lock (padlock)
{
XmlSerializer serializer;
if (!cache.TryGetValue(key, out serializer))
serializer = cache[key] = CreateSerializer(rootType, rootName, rootNamespace);
return serializer;
}
}
}
Here is a simple test case:
[XmlRoot("ChargeParty", Namespace = ManifestHeader.XmlNamespace)]
[XmlType("ChargeParty", Namespace = ManifestHeader.XmlNamespace)]
public sealed class ChargeParty : AParty
{
[XmlAttribute]
public override string Role
{
get
{
return "CHARGE";
}
set
{
}
}
public bool IsCharging { get; set; }
}
[XmlRoot("SenderParty", Namespace = ManifestHeader.XmlNamespace)]
[XmlType("SenderParty", Namespace = ManifestHeader.XmlNamespace)]
public sealed class SenderParty : AParty
{
[XmlAttribute]
public override string Role
{
get
{
return "SENDER";
}
set
{
}
}
public string SenderName { get; set; }
}
public static class TestClass
{
public static void Test()
{
var manifest = new ManifestHeader
{
AProperty = "A property",
ZProperty = "Z Property",
Parties = new AParty[] { new SenderParty { SenderName = "Sender Name" }, new ChargeParty { IsCharging = true }, new SenderParty { SenderName = "Another Sender Name" }, new SenderParty { SenderName = "Yet Another Sender Name" }, new ChargeParty { IsCharging = false } }
};
var xml = manifest.GetXml(ManifestHeader.GetXmlSerializerNamespaces());
Debug.WriteLine(xml);
}
}
Which produces:
<ManifestHeader xmlns="MyNamespace">
<ZProperty>Z Property</ZProperty>
<Party Role="SENDER">
<SenderName>Sender Name</SenderName>
</Party>
<Party Role="CHARGE">
<IsCharging>true</IsCharging>
</Party>
<Party Role="SENDER">
<SenderName>Another Sender Name</SenderName>
</Party>
<Party Role="SENDER">
<SenderName>Yet Another Sender Name</SenderName>
</Party>
<Party Role="CHARGE">
<IsCharging>true</IsCharging>
</Party>
<AProperty>A property</AProperty>
</ManifestHeader>