31

I have a recurring problem when passing Serialized objects between non-.NET Clients, and .NET WCF Services.

When WCF Deserializes objects, it is strictly dependant upon the order of properties.

That is, if I define my class as:

public class Foo 
{ 
  public int ID { get; set; } 
  public int Bar { get; set; } 
} 

Then WCF Will serialize the object as as:

<Foo>
  <Bar>123</Bar>
  <ID>456</ID>
</Foo>

Note: The properties are serialized in alphabetical order.

If you attempt to Deserialize an object which has the positions of Bar and ID swapped, WCF will treat the incorrectly positioned elements as null.

Whilst I know I can use the DataMember attribute, and force a specific ordering, I want to reduce the number of times I have to debug issues where fields are 'mysteriously' null.

So, my question is: Can you tell the WCF Deserializer to ignore the order of fields, when deserializing objects.

4 Answers4

12

You can specify the serialization order by decorating the elements in your data contract:

[DataContract]
public class Foo 
{ 
  [DataMember(Order=1)]
  public int ID { get; set; } 

  [DataMember(Order=2)]
  public int Bar { get; set; } 
}

So you can make sure the serialization order is the same all the time. But there is no way to tell the deserializer to "forget" about the order - the point is: this is handled by means of an XML schema, and done using the <xs:sequence> element - and that does imply and require order. You cannot just turn that off, I'm afraid.

Based on that XML schema, your non-.NET clients should be able to verify whether or not their XML that they're about to send conforms to that schema - and if it doesn't because the Bar and ID element have been swapped, they shouldn't be sending that invalid XML.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • Ah, thanks for the info about ``. I hadn't dug into the schema definition being generated. –  Nov 13 '09 at 09:42
11

You can use the IsRequired property of the DataMember attribute to specify that the elements are required. In that way, instead of getting "mysterious" null value, you'll get a more explicit error message indicating that a required element is missing.

[DataContract]
public class Foo 
{ 
  [DataMember(IsRequired=true, Order=1)]
  public int ID { get; set; } 

  [DataMember(IsRequired=true, Order=2)]
  public int Bar { get; set; } 
}

What's happening in your case is:

  • The DataContract expects Bar and ID elements in that order (alphabetic because you haven't specified an explicit order).

  • It encounters an ID element without a preceding Bar element. Since Bar is not required, it simply ignores it.

  • The ID element following Bar is ignored because it is in the wrong position.

Having said that, setting IsRequired to true will only help in version 1 of your contract. Elements added in subsequent versions will normally have IsRequired set to false. MSDN has an article on data contract versioning.

Joe
  • 122,218
  • 32
  • 205
  • 338
  • Thanks - the problem is, sometimes it's valid for the elements to be missing. Ohwell - IsRequired might be simpler solution where I can do it. –  Nov 13 '09 at 09:44
7

There's an old thread on this here:

http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/a891928b-d27a-4ef2-83b3-ee407c6b9187

Looks like the only option is to swap the serializer, but then it becomes opt in which is even more annoying.

Edit: you could write your own serializer to re-order the elements and then pass it on to DataContractSerializer.

Paul Stovell
  • 32,377
  • 16
  • 80
  • 108
  • 3
    Interesting. Now just to write an interceptor that grabs the message, interrogates the WCF mehtod to figure out what type was expected, deserialize using XmlSerializer (which according to the thread does work), and re-serialize using DataContractSerializer. How simple ;) Still... probably the only solution that would actually work as expected. Thanks :) –  Nov 13 '09 at 09:47
  • @WillHughes - I'm intrigued how you got on with your idea above? – Reddog Apr 11 '12 at 22:37
  • @Reddog I never implemented it - difficult and it's pretty inefficient (deserialize, reflection, reserialize... just so it can be deserialized again.) An alternative would be to swap the serializer, [as mentioned here](http://weblogs.thinktecture.com/cweyer/2010/12/using-jsonnet-as-a-default-serializer-in-wcf-httpwebrest-vnext.html). –  Apr 11 '12 at 23:37
  • 1
    Interesting. I have the opposite problem. I have a WCF generated client proxy has Order=x attributes base on the sequence in the server WSDL, but the data returned often leaves out empty/null fields (incorrectly). Regardless this fails in the gen'd proxy. To make this work I explicitly removed the Order= attributes from the generated proxy and now the deserialization works properly. So it seems that DCS does seem to understand out of order results as long as the contract does not have an Order= attribute. This is on .NET 4.0 - since this thread is old something may have changed. – Rick Strahl Mar 15 '13 at 03:16
  • 1
    This was a good resource. I have a situation where i dont have control on the service being generated (and orders can change within it). If i remove "All" the Order attributes values in Reference.cs, i get all the values deserailized :) Wonder if there is any tool to remove the OrderBy's in the reference.cs – Looneystar Jan 17 '14 at 04:23
  • 1
    We were able to remove the Order property values on the attributes in Reference.cs to get the deserializer to ignore the sequence, like Looneystar. – BrettJ Dec 01 '16 at 13:03
0

For me, the DataContractSerializer has been my savior with .NET Framework 4.0

When referencing an old asmx web service, we had null values for all the fields that were "after" inserted new fields in the server wsdl. So whenever there were new fields added on server side, that was nullifying several values on client side. This was forcing us into updating our reference in our code to always stick to the server wsdl.

I found two solutions to make our client tolerant to new fields in the xml stream:

  • Either use an old web reference in visual studio (old client code generator for compatibility)
  • Or use a WCF classic reference, but change the serializer in the svcmap file. ClientOptions -> Serializer = "DataContractSerializer" instead of "Auto". I found out that in our case, the "Auto" value was the same as "XmlSerializer" value.
vlabatut
  • 178
  • 8