Tuesday, April 20, 2010

WCF Serialization Basics - Opt In

This post is part of a series of posts that I am writing as I prepare for a presentation on WCF basics.

Prior to WCF, when exposing a web service, serialization was done using the SoapFormatter class. Using the SoapFormatter, the developer would tag the class being serialized with the Serializable attribute, and the SoapFormatter would serialize all of the fields in the class, regardless of visibility, unless you opted out by assigning the NonSerialized attribute to a field that you wanted to excluded.


//Add reference to:
//System.Runtime.Serialization.Formatters.Soap.dll

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

[Serializable]
public class Person
{
//Note: Cannot assign this to a property.
//Must use field back property
[NonSerializedAttribute]
private string _lastname;

public int ID {get;set;}

public string FirstName {get;set;}
public string LastName
{
get {return _lastname;}
set {_lastname = value;}
}
}

public class TestApp
{
public static void Main()
{
try
{
var person = new Person();
person.ID = 1;
person.FirstName = "Fred";
person.LastName = "Flintstone";


using(var memStrm = new MemoryStream())
{
IFormatter formatter =
new SoapFormatter();
formatter.Serialize(memStrm, person);

var data = new byte[memStrm.Length];
Array.Copy(memStrm.GetBuffer(),
data, data.Length);
var text =
Encoding.UTF8.GetString(data);
Console.WriteLine(text);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
Console.Write("Press any key...");
Console.ReadKey();
}
}
}




As you can see in the output above, the _lastname field was not serialized by the SoapFormatter because it was tagged with the NonSerialized attribute.

One of the problems with the SoapFormatter's opt-out approach is that every time you add a field to a class that is serializable, you have to consider whether or not the new field is serializable. If it is not, you will not get an error until run-time. The developer also has to remember that when the a new auto-implemented property is added to a class, the compiler adds fields to the class behind the scenes. This is true for both the "ID" and "FirstName" properties in the example above as is evident in the long, awkward xml node names that end with "BackingField."

To get around this, WFC's DataContractSerializer follows an opt-in approach. With the DataContractSerializer, the developer tags the class being serialized with the DataContract attribute, and then tags the fields and/or attributes that should be serialized with the DataMember attribute. Only the fields and/or properties that were tagged with the DataMember attribute will be included in the serialized stream.


//Add reference to System.Runtime.Serialization.dll

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

[DataContract]
public class Person
{
//demonstrates serialization of a field
[DataMember]
private string _firstName;

//demonstrates serialization of a property
[DataMember]
public int ID {get;set;}

public string FirstName
{
get {return _firstName;}   
set {_firstName = value;}
}

//demonstrates data that was not serialized
public string LastName {get;set;}
}

public class TestApp
{
public static void Main()
{
try
{
var person = new Person();
person.ID = 1;
person.FirstName = "Fred";
person.LastName = "Flintstone";


using(var memStrm = new MemoryStream())
{
var pType = person.GetType();
var serializer =
new DataContractSerializer(pType);
serializer
.WriteObject(memStrm, person);

var data = new byte[memStrm.Length];
Array.Copy(memStrm.GetBuffer(),
data, data.Length);
var text =
Encoding.UTF8.GetString(data);
Console.WriteLine(text);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
Console.Write("Press any key...");
Console.ReadKey();
}
}
}



As you can see in the above example, the LastName property was not serialized because it did not have the DataMember attribute. Also notice that the "ID" property was serialized at the property level, but the first name was serialized at the field name.



WCF added the flexibility to serialize not only at the field level, but also at the property level. This flexibility adds a lot of power, but it can also lead to unexpected results. Be careful not to add the DataMember attribute to both the property and its backing field. In this situation, both will be serialized. Not only does this clutter your data contract and add unnecessary traffic over the wire, but it also can lead to weird behavior or an error.



WCF's opt-in approach also helps immensely in practice because it complies with the third tenet of Service Oriented Architecture that states "Services share schema and contract, not class." When you chose to add the DataMember attribute to a member of a DataContract class, you are choosing to change the contract. This was not the case when you decided to add a private field to a class marked Serializable.

No comments:

Post a Comment