15

Is there a way to cause XmlSerializer to serialize primitive class members (e.g. string properties) as XML attributes, not as XML elements, without having to write [XmlAttribute] in front of each property declaration? I.e. is there a global switch that tells XmlSerializer to serialize all primitive class members as XML attributes?

Assume that we have the following class:

public class Person
{
    public string FirstName
    {
       ...
    }

    public string LastName
    {
       ...
    }
}

Then XmlSerializer generates this code by default:

<Person>
    <FirstName>John</FirstName>
    <LastName>Doe</LastName>
</Person>

What I want, however, is this code:

<Person FirstName="John" LastName="Doe"/>

Again: I want to do that without [XmlAttribute] (or without XmlAttributeOverrides, which would be even more work).

One possible solution would be to use a generic postprocessing step that applies an XSLT transform to convert elements to attributes. But I wonder whether there is a simpler solution.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • Enrico's answer seems to be a solution to the question, but it is certainly more complicated and less desirable than the two you are avoiding. Perhaps you can tell us why you can't use those methods, so that we know what, specifically, you're avoiding? – Kyle W Aug 03 '11 at 20:58
  • I need to exchange data with an external sytsem that requires a specific XML format. In that format, simple data types are represented as attributes, complex data types (e.g. lists) are represented as attributes. The data model has about 50 classes and 500 attributes. I want to avoid that I have to write [XmlAttribute] in front of each attribute. –  Aug 03 '11 at 21:13
  • @Kyle W In this case having the common XML serialization logic in one place (like a base class) would definitely make sense, since it keeps things [DRY](http://en.wikipedia.org/wiki/Don't_repeat_yourself). – Enrico Campidoglio Aug 03 '11 at 21:24
  • @Enrico I'm not sure this counts as repetition... at least not in how it relates to "DRY". fmunkert: Honestly I'd personally put all the tags there myself, or write something that could parse stuff and create the classes for me. Copy/pasting XmlAttribute is going to be a small portion of actually creating the classes. But YMMV. – Kyle W Aug 04 '11 at 00:19

2 Answers2

4

One way to achieve this is to implement the serialization logic in a base class that implements the IXmlSerializable interface. The classes that are to be serialized to XML, would then have to derive from this base class in order to get the functionality.

Here's an example

public class XmlSerializableEntity : IXmlSerializable
{
    public XmlSchema GetSchema()
    {
        // Implementation omitted for clarity
    }

    public void ReadXml(XmlReader reader)
    {
        // Implementation omitted for clarity
    }

    public void WriteXml(XmlWriter writer)
    {
        var properties = from property in this.GetType().GetProperties()
                         where property.PropertyType.IsPrimitive ||
                               property.PropertyType == typeof(string)
                         select property;

        foreach (var property in properties)
        {
            var name = property.Name;
            var value = property.GetValue(this, null).ToString();
            writer.WriteAttributeString(name, value);
        }
    }
}

Here we are using Reflection to get a list of properties from the current object, whose type is a primitive or a String. These properties are then written to the XML output as attributes using the provided XmlWriter object.

The classes to be serialized would simply have to inherit from XmlSerializableEntity to automatically get this behavior:

[Serializable]
public class Foo : XmlSerializableEntity
{
    public int Bar { get; set; }
}
Enrico Campidoglio
  • 56,676
  • 12
  • 126
  • 154
  • 1
    The disadvantage of this solution is that other XML serialization attributes like [XmlIgnore] no longer would work in classes derived from XmlSerializableEntity (unless I would reimplement all the Attribute processing that XmlSerializer does in XmlSerializableEntity). –  Aug 03 '11 at 21:18
  • @fmunkert Indeed, you would have to check for the [XmlIgnoreAttribute](http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlignoreattribute.aspx) yourself via Reflection in the `XmlSerializableEntity` class. However having this kind of specialized shared behavior in a central location would definitely benefit future modifications. – Enrico Campidoglio Aug 03 '11 at 21:32
  • I am accepting this solution, because it answers my question. I will implement it, however, differently because Enrico's solution would require too much work if I still want to be able to use all Xml*Attribute classes. I am thinking about generating an XmlAttributeOverrides instance dynamically using reflection that overrides all primitive properties; or alternative, I might write code to auto-generate proxy classes using CodeDom. –  Aug 05 '11 at 07:06
0

I think Xslt is the most stable, easy to maintain and elegant way to go. It does not require re-basing everything, relies on already tested serialization, allows xml custom attributes in the class, and can be done in memory with a compiled transform.

Here is some generic xslt that ought to do it:

<?xml version=’1.0′ encoding=’utf-8′?>
<xsl:stylesheet version=’1.0′ xmlns:xsl=’http://www.w3.org/1999/XSL/Transform’ xmlns:msxsl=’urn:schemas-microsoft-com:xslt’ exclude-result-prefixes=’msxsl’>
<xsl:template match=’*'>
<xsl:copy>
<xsl:for-each select=’@*|*[not(* or @*)]‘>
<xsl:attribute name=’{name(.)}’><xsl:value-of select=’.'/>
</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates select=’*[* or @*]|text()’/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Having said that, I wonder if elements are slower than attributes, especially when sent across the line.

toddmo
  • 20,682
  • 14
  • 97
  • 107