Kurt Claeys

DEVITECT.NET

  Home  |   Contact  |   Syndication    |   Login
  100 Posts | 42 Stories | 62 Comments | 14 Trackbacks

News

I'm a .NET architect / developer from Belgium an also trainer in .NET topics.

Kurt CLAEYS

 


 

Join Me at Tech·Ed EMEA Connect for Developers!

View Kurt CLAEYS's profile on LinkedIn


Being ...





 


Working ...


and



Reading ...



Riding ...

Article Categories

Archives

Links

WCF uses by default the DataContractSerializer found in System.Runtime.Serialization namespace to serialize an object to XML and back to an object. This is needed to transfer the data in the object through the communication channel as part of the SOAP packet.

The DataContractSerializer supports version tolerance which can be very helpful when versions of WCF Clients are not aligned with the versions of their needed WCF Services. It’s not always possible to upgrade all the clients at once when you deploy a new version of the service. Or maybe an already upgraded client must be able to talk to a previous version of the service and expect data back from it (roundtripping).

You can also use this DataContractSerializer yourself without needing WCF as the infrastructure around it. Based on this idea I started to do some research on how this versioning tolerance works and how it can be implemented.

First I created two generic methods which will do the serialization and deserialization. Very usefull methods I include as helperclasses in every WCF project I do.

* SerializeToString takes an object from a generic type as input and returns the XML containing the data as a string.

static public string SerializeToString(T o)
{
    DataContractSerializer dataContractSerializer = new DataContractSerializer(typeof(T));

    StringWriter stringWriter = new StringWriter();

    XmlWriter xmlWriter = XmlWriter.Create(stringWriter);

    dataContractSerializer.WriteObject(xmlWriter, o);
    xmlWriter.Close();

    return (stringWriter.ToString());
}

* DeserializeFromString takes the XML string with the serialized data en creates an object out of this data.

static public T DeserializeFromString(string Xml)
{
    DataContractSerializer dataContractSerializer = new DataContractSerializer(typeof(T));

    StringReader stringReader = new StringReader(Xml);
    XmlReader xmlReader = XmlReader.Create(stringReader);

    T obj = (T)dataContractSerializer.ReadObject(xmlReader);

    return obj;
}

Now ... Let’s consider some scenarios where we need version tolerance.

1. A second version of a class adds another field to it. When the object is serialized from XML created by the first version of the class, like in the case of the client being an older version compared to the service, we want to have a default injected in the missing field.

I’m using a very simple class called PersonClassV1 here.

[DataContract(Name = "Person", Namespace = "PersonNamespace")]
public class PersonClassV1 
{
    [DataMember]
    public string Name;
}

Version 2 adds a country field and also has a method which is attributed with the OnDeserializing attribute. This method will be executed when the DataContractSerializer deserializes the given XML. So it’s easy to check if the country field is not present and give it a default. The operation receiving this object would not see the difference as the country field is given this value before the data enters the operation.

[DataContract(Name = "Person", Namespace = "PersonNamespace")]
public class PersonClassV2 
{
    [DataMember]
    public string Name;

    [DataMember]
    public System.String Country;

    [OnDeserializing()]
    internal void OnDeserializing(StreamingContext context)
    {
        if (Country == null)
        {
            Country = "BE";
        }
    }
}

Notice also that both classes have been given the same name and namespace to simulate how you would work when updating a single datacontract of existing services.

In this drawing the arrow represent the serialization and deserialization.

image

The code for testing this :

PersonClassV1 p1;
p1 = new PersonClassV1();
p1.Name = "Kurt";         

string asXMLv1;
asXMLv1 = GenericDataContractSerializer<PersonClassV1>.SerializeToString(p1);
textBox1.Text = asXMLv1;

PersonClassV2 p2;
p2 = GenericDataContractSerializer<PersonClassV2>.DeserializeFromString(asXMLv1);
//Deserialize the XML from version 1 to an object if version 2
MessageBox.Show(p2.Name + " " + p2.Country);

This shows my name and the countrycode BE which is the given default in the messagebox.

As you can see there is no breaking change in the datacontracts here. Older clients can still send their data to services expecting a newer version. The new version of the datacontract on the service is responsible for defining the defaults.

 

2. A second version of the datacontract is on the client, but the service still uses the first version. The service gets some data in the serialized stream that it doesn’t know and cannot use. Of course the service does not know what it does not know so there’s no logic to use this extra field. WCF also supports version tolerance here, no breaking change. Not really a problem.

But what about roundtrips ? Suppose the operation returns this class again. When returning this class to the client the DataContractSerializer is used to serialize the class to XML. But as it doesn’t know the countryfield this field will be lost and be empty at the client altough the class at the client knows the field.

How can we work this out so that this extra field is not lost during this roundtripping ? The solution to this is to implement the IExtensibleDataObject interface. By implementing this interface a field called ExtensionData which is of type System.Runtime.Serialization.ExtensionDataObject is needed. This field is acting as a bag for the extra XML that is present in the data. This data becomes part of the datacontract although it not there to be used because the lower version of the service will not have logic for the additional data given by the second version of the dataContract. This ExtensionData object is there for roundtripping. When deserializing/serializing this class against the datactontract that has this extra field, the field is filled up with the data from the extensiondata field. The extensiondata field simply holds the XML data (at the service) that it cannot deserialize so it's there again when the data is serialized again at the client.

Code :

[DataContract(Name = "Person", Namespace = "PersonNamespace")]
public class PersonClassV1 : IExtensibleDataObject
{
    [DataMember]
    public string Name;

    #region IExtensibleDataObject Members

    private ExtensionDataObject extensionData;

    public ExtensionDataObject ExtensionData
    {
        get
        {
            return extensionData;
        }
        set
        {
            extensionData = value;
        }
    }

    #endregion
}

 

In this schema you see the serialization from version 2, deserializing this XML into version 1, serializing this again to XML and deserializing this XML again to an object of version 2.

image

Code to test this scenario :

PersonClassV2 p2;
p2 = new PersonClassV2();
p2.Name = "Kurt";       
p2.Country = "US";

string asXMLv2;
asXMLv2 = GenericDataContractSerializer<PersonClassV2>.SerializeToString(p2);

PersonClassV1 p1;
p1 = GenericDataContractSerializer<PersonClassV1>.DeserializeFromString(asXMLv2);

string asXMLv1;
asXMLv1 = GenericDataContractSerializer<PersonClassV1>.SerializeToString(p1);

PersonClassV2 p2bis;
p2bis = GenericDataContractSerializer<PersonClassV2>.DeserializeFromString(asXMLv1);

MessageBox.Show(p2bis.Country);

This shows me "US", the value of the country field given to the first instantiation of version 2.

Conclusion : Version 1 does not know the country field but can remember it in the extensionData field to spit it out when serialized so it's there when deserialized into the second version again.

You could say that it's a good practice in WCF development to always implement IExtensibleDataObject on your datacontracts.

posted on Friday, May 02, 2008 3:22 AM

Feedback

# re: WCF : DataContract versioning tolerance with the [OnDeserializing()] method and the IExtensibleDataObject Interface. 5/8/2008 4:14 AM Steve Degosserie
Hi Kurt,

Implementing IExtensibleDataObject on all DataContracts might open up WCF services to DOS attacks. If an attacker is passing huge data, in addition to the members of the DataContract, WCF will still process (deserialize / serialize) the full messages, consuming of a lot of CPU time for nothing.


Post Feedback

Title:
Name:
Email: (never displayed)
Url:
Comments: 
Please add 2 and 3 and type the answer here: