views:

44

answers:

3

We've already established previously that DCS serializes/deserializes objects alphabetically. However, after further investigation I've discoverred this is not entirely true.

If we have a structure like this:

[DataContract]
public class Content
{
    [DataMember]
    public string Title;
    [DataMember]
    public string Slug;
    [DataMember]
    public string Description;
}

[DataContract]
public class TextContent : Content
{
    [DataMember]
    public string Text; 
}

and have an object of type TextContent to serialize, DCS will do this:

<Content i:type="TextContent" ...>
<Description>desc</Description>
<Slug>some-slug</Slug>
<Title>content title</Title>
<Text>some content</Text>
</Content>

So as you can see, the property from the inheriting class is attached to the end of the serialized XML fragment even though it should be before Title. DCS doesn't go over the combined properties and reorder them. I've noticed this when I was manually adding Text element in front of Title element and deserialization just didn't want to work. That's why I performed a serialization of a new object and figured this out.

My questions are:

  1. This can't be common knowledge?? Anyone else noticed this?
  2. Anyone knows of a better serializer (all I ever find if I search for it is the old XmlSerializer and DCS) because this issue with DCS's ordering is extremely annoying? I know we can use the Order attribute but that only enables us to align with one external XML source. What if we have three, four or more third party XML providers which all generate perfectly valid XML but our app is nitpicking about elements order (because of DCS)?
A: 

There's the NetDataContractSerializer but the only difference between it and the DCS is that it enables type sharing between the client and the server but you lose the forward compatibility because both sides have to serialize/deserialize into the same type..

There's also the C# wrapper for protocol buffer on codeplex: http://code.google.com/p/protobuf-net/

I haven't tried it out myself, but it's supposed to be much faster and lightweight. As to your actual questions:

  1. doubt it, I certainly never noticed this :-P
  2. can you give an example where the ordering of elements actually mattered? I haven't come across this myself (I guess that's why most of us haven't noticed this behavior..), but with the proto-buf serializer this will undoubtly be a problem..
theburningmonk
if you check out the question that I linked to (link on previously) you will be able to follow on with samples and even a blog post where the author explains this issue in high detail and it's easily reproducable - just reorder some elements in serialized XML and try to deserialize and you will get inconsistent behaviour (object properties might be null or have strange values even though in your XML everything is fine). I knew about this but now I've noticed even more issues, as stated in the above example with base and child class.
mare
The protobuf looks interesting, never knew that something like this existed, but it's binary, so I cannot use it just now.
mare
+2  A: 

The base types are always first in the order. You can define the order of the serialized properties of a object with respect of the Order property of the DataMember attribute (see http://msdn.microsoft.com/en-us/library/ms729813.aspx)

Oleg
I guess this is the correct answer, though no explanation about why are the base types first in the order if the inheriting type has a property that should alphabetically be in front of some base type's properties. I guess that's just the way it is with DCS...
mare
In http://msdn.microsoft.com/en-us/library/ms729813.aspx you can reed in the list of "Basic Rules": "If a data contract type is a part of an inheritance hierarchy, data members of its base types are always first in the order.". So it is just a part of data contract implementation. So no explanation, just a fact.
Oleg
nicely pointed out
mare
A: 

If you need to be able to serialize to match an external schema, then you obviously shouldn't be using the DataContractSerializer. That's not what it's for.

You can either use the XmlSerializer, which is intended to give you more control over the serialized XML, or implement IXmlSerializable, and gain complete control over the XML, or you can write your own custom serialization using LINQ to XML. This will let you do exactly what you mention - serialize the same data in different ways. Example:

Data

internal class Person
{
    internal string Name { get; set; }
    internal string Telephone { get; set; }
    internal Address HomeAddress { get; set; }
    internal Address WorkAddress { get; set; }
}

internal class Address
{
    internal string Line1 { get; set; }
    internal string Line2 { get; set; }
    internal string City { get; set; }
    internal string State { get; set; }
    internal string PostalCode { get; set; }
}

Test Program

private static void Main()
{
    var person = new Person
                     {
                         Name = "John Saunders",
                         Telephone = "something",
                         HomeAddress = new Address
                                           {
                                               Line1 = "Line 1",
                                               Line2 = "Line 2",
                                               City = "SomeCity",
                                               State = "SS",
                                               PostalCode = "99999-9999",
                                           },
                         WorkAddress = new Address
                                           {
                                               Line1 = "Line 1a",
                                               Line2 = "Line 2a",
                                               City = "SomeCitay",
                                               State = "Sa",
                                               PostalCode = "99999-999a",
                                           },
                     };
    XDocument personWithElements = SerializeAsElements(person);
    personWithElements.Save("PersonWithElements.xml");

    XDocument personWithAttributes = SerializeAsAttributes(person);
    personWithAttributes.Save("PersonWithAttributes.xml");
}

Serialization as Elements:

private static XDocument SerializeAsElements(Person person)
{
    return new XDocument(
        new XElement("Person",
                     new XElement("Name", person.Name),
                     new XElement("Telephone", person.Telephone),
                     SerializeAddressAsElements(person.HomeAddress, "HomeAddress"),
                     SerializeAddressAsElements(person.WorkAddress, "WorkAddress"))
        );
}

private static XElement SerializeAddressAsElements(Address address, string elementName)
{
    return new XElement(elementName,
                        new XElement("Line1", address.Line1),
                        new XElement("Line2", address.Line2),
                        new XElement("City", address.City),
                        new XElement("State", address.State),
                        new XElement("PostalCode", address.PostalCode)
        );
}

Serialization as Attributes:

private static XDocument SerializeAsAttributes(Person person)
{
    return new XDocument(
        new XElement("Person",
                     new XAttribute("Name", person.Name),
                     new XAttribute("Telephone", person.Telephone),
                     SerializeAddressAsAttributes(person.HomeAddress, "HomeAddress"),
                     SerializeAddressAsAttributes(person.WorkAddress, "WorkAddress"))
        );
}

private static XElement SerializeAddressAsAttributes(Address address, string elementName)
{
    return new XElement(elementName,
                        new XAttribute("Line1", address.Line1),
                        new XAttribute("Line2", address.Line2),
                        new XAttribute("City", address.City),
                        new XAttribute("State", address.State),
                        new XAttribute("PostalCode", address.PostalCode)
        );
}

PersonWithElements:

<?xml version="1.0" encoding="utf-8"?>
<Person>
  <Name>John Saunders</Name>
  <Telephone>somethine</Telephone>
  <HomeAddress>
    <Line1>Line 1</Line1>
    <Line2>Line 2</Line2>
    <City>SomeCity</City>
    <State>SS</State>
    <PostalCode>99999-9999</PostalCode>
  </HomeAddress>
  <WorkAddress>
    <Line1>Line 1a</Line1>
    <Line2>Line 2a</Line2>
    <City>SomeCitay</City>
    <State>Sa</State>
    <PostalCode>99999-999a</PostalCode>
  </WorkAddress>
</Person>

PersonWithAttributes:

<?xml version="1.0" encoding="utf-8"?>
<Person Name="John Saunders" Telephone="somethine">
  <HomeAddress Line1="Line 1" Line2="Line 2" City="SomeCity" State="SS" PostalCode="99999-9999" />
  <WorkAddress Line1="Line 1a" Line2="Line 2a" City="SomeCitay" State="Sa" PostalCode="99999-999a" />
</Person>
John Saunders
I've used LINQ to XML with one project so far and it worked great.
mare