1

I'm trying to serialise to the following XML:

...
<ManifestHeader>
    <Party Role="CHARGE">
        <f:Name>Name1</f:Name>
        ...
    </Party>
    <Party Role="SENDER">
        <Name>Name2</Name>
        ...
    </Party>
</ManifestHeader>
...

But I'm getting some unwanted attributes:

<ManifestHeader>
    <Party d4p1:type="PickupParty" Role="CHARGE" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance">
        <f:Name>Name1</f:Name>
        ...

How do I get rid of the d4p1:type and xmlns:d4p1 attributes?

I have an abstract class AParty with two subclasses ChargeParty and SenderParty.

In my ManifestHeader class I have:

    [XmlElement("Party")]
    public AParty[] Parties;

I use XmlInclude as follows:

[XmlInclude(typeof(PickupParty))]
[XmlInclude(typeof(SenderParty))]

In my serialzer I use a custom namespace:

serialiser.Serialize(file, this, nameSpace);

Any ideas?


Edit

I checked out that question before posting my question. Firstly I'm using XmlSerializer not DataContractSerializer, and secondly I've successfully set the namespace for all of my objects except for those I've included with XmlInclude. Hence this question.

Community
  • 1
  • 1
Felix
  • 3,783
  • 5
  • 34
  • 53
  • possible duplicate of [Remove "d1p1" namespace prefix in DataContractSerializer XML output](http://stackoverflow.com/questions/9392555/remove-d1p1-namespace-prefix-in-datacontractserializer-xml-output) – a'' Mar 26 '15 at 07:00
  • @erem I don't reckon it's a duplicate, see my edit. – Felix Mar 26 '15 at 19:17
  • Do you also need to deserialize? – dbc Mar 28 '15 at 22:33
  • @dbc Nah, only need to serialize. – Felix Mar 29 '15 at 04:00
  • Why do you want to get rid of those attributes? – John Saunders Mar 30 '15 at 02:15
  • I'm trying to import to an application via XML, so I'm following the specification for their XML import file. Their specification doesn't include the attributes, so I didn't want them either. Hm, I never actually considered just leaving them in and seeing whether it works. – Felix Mar 30 '15 at 02:42

2 Answers2

3

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>
dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thanks so much for the detailed answer! I've tried running your solution in it's own project, but in the line `var xml = manifest.GetXml(ManifestHeader.GetXmlSerializerNamespaces());` I get an error: `Error 1 'XmlSerialisation.ManifestHeader' does not contain a definition for 'GetXml' and no extension method 'GetXml' accepting a first argument of type 'XmlSerialisation.ManifestHeader' could be found (are you missing a using directive or an assembly reference?) C:\ws\XmlSerialisation\XmlSerialisation\Program.cs 61 32 XmlSerialisation`. Any ideas? – Felix Mar 30 '15 at 01:54
  • @Felix - that's an extension method I use for serializing to XML for testing purposes. Included it. – dbc Mar 30 '15 at 02:09
1

This is a workaround, not really a solution.

Instead of using a base class and subclasses I used just a single big class, which avoids the need for XmlInclude, which avoids the unwanted attributes (d4p1:type and xmlns:d4p1).

The single big class I'm using basically has all the properties of the previous subclasses combined. Depending on which role the class is, only a subset of properties are use.

This is pretty ugly, so if anyone has a proper solution that'd be mean!

Felix
  • 3,783
  • 5
  • 34
  • 53