Serialization in Windows Communication Foundation
Introduction:
In most scenarios, when we design a service, we create a service contract and data contracts for the types used in the service. We let the service description be generated by the service contract, and we won’t ever have to look at the underlying XML, but sometimes, things don’t go that well in situations like preexisting types which implement SerializableAttribute and ISerializable, mapping an incoming message to types which are not serializable or which don’t match the predefined schema.
Internally, WCF represents all the messages with the Message class which is defined in the System.ServiceModel.Channels namespace. The Message class generate a SOAP envelop which consists of a message header and a message body. The message body contains the payload. The Message class provides an interface to interact with the message body or the message header.
The most common way of serialization in a WCF service is to mark the type with the DataContract attribute and mark each member which is to be serialized with the DataMember attribute.
Types that support serialization:
- Types marked with DataContractAttribute or CollectionDataContractAttribute.
- Types decorated with SerializableAttribute which can optionally implement the ISerializable or IXmlSerializable interface.
- MessageContract decorated with MessageContractAttribute and containing the data contracts or the serializable types as header or body.
- We can include any of these types, but the resulting message format will change based on the choice of the serializer.
There are two types of serializers: DataContractSerializer and XmlSerializer. By default, WCF uses DataContractSerializer for message serialization. Here are the differences between both the serializers.
- In the case of DataContract, the DataContractSerializer serializes the fields marked with the DataMember attribute (irrespective of public or private), while XmlSerializer serializes only public properties and fields.
- In the case of SerializableAttribute, the DataContractSerializer serializes all fields, while XmlSerializer serializes only the public fields.
- In the case of the IXmlserializable interface, we provide the schema and control the serialization.
- In the case of MessageContract, the DataContractSerializer serializes members marked with the header or body attribute, while XmlSerializer serializes only the public properties or fields.
- In the case of DataContractSerializer, constructors are not called during deserialization. However, if you need to perform initialization, DataContractSerializer supports the [OnDeserializing], [OnDeserialized], [OnSerializing], and [OnSerialized] callbacks that were also supported on the Binary/SoapFormatter classes.
Message encoding:
There exists two types of encoding, Document/literal and RPC. Actually, they are two programming models, one is the RPC programming model and the other is the Message Passing programming model. One programming model says that if you are talking to a service, you should always explicitly form a message and use explicit message-passing infrastructure to send the message to the service and, optionally, to receive the response. The following is an example of a potential implementation of such a mechanism (from the caller's perspective):
Channel chan = new ChannelFactory.CreateChannel(endpointAddress);
ActionInvocationMessage msg = new ActionInvocationMessage();
msg.Action = "SayHello";
msg.Parameters.Add(new StringParameter("Forename", "Rich"));
msg.Parameters.Add(new StringParameter("Surname", "Turner"));
Console.WriteLine(chan.SendMessageSync(msg, new SayHelloCallback(HandleResponse)));
The other camp aims to simplify the developer experience by reusing development constructs in wide use today, that results in code such as the following:
HelloWorldProxy proxy = new HelloWorldProxy(endpointAddress);
Console.WriteLine(proxy.SayHello("Rich", "Turner"));
In both the DataContractSerializer and the XmlSerializer, default the value of style is document/literal format, but we can use RPC encoding using the OperationFormatStyle enumeration in the event you need it. Each serializer has a style property based on the enumeration, and can be set to one of its two elements, Document and RPC.
DataContractFormatAttribute:
We can use the RPC style like this:
[ServiceContract(Name="ServiceContract",
Namespace="https://www.ServiceContract.com/Samples/RPCExample")]
[DataContractFormat(Style=OperationFormatStyle.Rpc)]
public interface IRPCStyleTest
XmlSerializerFormatAttribute:
We can support the RPC style in the case of XmlSerializer like this:
[ServiceContract(Name="ServiceContract",
Namespace="https://www.ServiceContract.com/Samples/RPCExample")]
[XmlSerializerFormat(Style=OperationFormatStyle.Rpc,
Use=OperationFormatUse.Encoded)]
public interface IRPCStyleTest
Serialization/Encoding:
Actually, there exists two contracts when discussing .NET types in the context of serialization. First is the .NET contract which has properties, methods, and constructors, and the other is the contract which has the serializable properties and which would be exposed to the word. Each serializer comes with some algorithms which define the mapping details, and includes attributes that let us customize the mapping. This mapping is stored with the metadata which serializers can access at runtime and decide which property to serialize.
There exists an inbuilt tool (svc.exe) for moving between these contract representations. We can use xsd.exe while working with XmlSerializer. Svc.exe can generate the .NET data type with all the attributes from the XML schema definition. If we pass a .NET assembly to svc.exe, it will automatically generate all the types which support serialization. So, we can write the data contracts in either XML schema or in .NET code.
Windows Communication Foundation lets us specify the encoding to be used. Serialization defines how the .NET object maps to XML, while Encoding defines how the XML is written out to a stream of bytes. WCF supports three types of encoding: text, binary, and message transmission optimization mechanism (MTOM). We can also add custom encoding.
If you represent the same logical data by the three encodings, the stream will be totally different, but they all represent the same logical data. Encoding isn’t considered to be part of the service contract, but it is the configuration entry. We can change it dynamically, and we can control it by endpoint binding. Different kinds of encoding are used in different types of word scenarios. If interoperability is a concern, then text type encoding is used, while if performance is the concern, then binary encoding can be used.
When WCF creates a message at runtime, it chooses the serializer based on the service contract, and selects the encoding based on the binding configuration. Let’s see how this is accomplished. We can take the following type for understanding the serializers:
[DataContract]
public class Name
{
[DataMember]
public string firstName;
public string middlename;
[DataMember]
public string lastName;
}
DataContractSerializer:
We can use the following method to generate the serialized stream from DataContractSerializer:
public static void SerializeClass()
{
Name name = new Name { firstName = "John",
middlename = "Fuma", lastName = "last" };
using (Message message = Message.CreateMessage(MessageVersion.Soap11WSAddressing10,
"https://www.ServiceContract.com/Samples/RPCExample/Name", name,
new DataContractSerializer(typeof(Name))))
{
using (FileStream fs = new FileStream("binary.bin", FileMode.Create))
{
using (XmlDictionaryWriter writer =
XmlDictionaryWriter.CreateBinaryWriter(fs))
{
message.WriteMessage(writer);
}
}
}
}
The CreateMessage method’s last parameter specifies the serializer. The serializer controls how the message body has the Name object as XML. The writer determines which encoding will be used to write the message out to the byte stream. XmlDictionaryWriter and XmlDictionaryReader can be used to read and write the type in any encoding (text, binary, MTOM).
The following method can be used to generate the Name type again from the bin file. We can use XmlDictionaryReader and the corresponding decoder to do the reverse engineering, and get the original message.
public static void ReadSerializeClass()
{
using (FileStream fs = File.OpenRead("binary.bin"))
{
using (XmlDictionaryReader reader =
XmlDictionaryReader.CreateBinaryReader(fs, XmlDictionaryReaderQuotas.Max))
{
using (Message message = Message.CreateMessage(
reader, 1024, MessageVersion.Soap11WSAddressing10))
{
// deserialize Person object from XML
Name name = message.GetBody(
new DataContractSerializer(typeof(Name)));
}
}
}
}
In case we don’t want to read the XML infoset directly, we can call GetReaderAtBodyContents instead of GetBody, which returns the XmlReader and reads the specific elements.
XmlSerializer:
XmlSerializer is quite an old technology which is used in web services. Windows Communication Foundation supports it for backwards compatibility. I am not going to discuss a lot about it as there are a lot of articles about it available already. Here is the sample code which serializes the same Name object. We can see that it includes the middle name too as middle name is the public property.
Serialization method:
public static void WriteNameXmlSerializer()
{
Name name = new Name { firstName = "John",
middlename = "Fuma", lastName = "last" };
using (FileStream fs = new FileStream("Name.xml", FileMode.Create))
using (XmlDictionaryWriter writer =
XmlDictionaryWriter.CreateTextWriter(fs))
{
XmlSerializer serializer = new XmlSerializer(typeof(Name));
serializer.Serialize(writer, name);
}
}
Serialized stream:
<Name xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="https://www.w3.org/2001/XMLSchema">
<firstName>John</firstName>
<middlename>Fuma</middlename>
<lastName>last</lastName>
</Name>
Deserialization method:
public static void ReadNameXmlSerializer()
{
using (FileStream fs = new FileStream("Name.xml", FileMode.Open))
using (XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(fs, XmlDictionaryReaderQuotas.Max))
{
XmlSerializer serializer = new XmlSerializer(typeof(Name));
Name person = (Name)serializer.Deserialize(reader);
}
}
NetDataContractSerializer:
DataContractSerializer has two modes: SharedContract and SharedType. The modes reflect whether you want to share the schema contract across the wire or the .NET type information.
In general, sharing schema contract is the preferred and recommended approach, as it loosens the coupling across the wire, but WCF still wants to support .NET Remoting style applications where maintaining type across the wire is important. WCF spilts both the modes into separate classes, so now, WCF has DataContractSerializer (SharedContract) and NetDataContractSerializer (SharedType). Since WCF wants to encourage DataContractSerializer, it is the default serializer.
NetDataContractSerializer supports the same mapping algorithm and the same customization attributes, but in the case of NetDataContractSerializer, type information is not required ahead of time as in DataContractSerializer. We can see it from the following code:
// no type specified
NetDataContractSerializer serializer = new NetDataContractSerializer();
serializer.WriteObject(writer, p);
We can use the following method to serialize the Name class:
public static void SerializeClass()
{
Name name = new Name { firstName = "John", middlename = "Fuma", lastName = "last" };
NetDataContractSerializer netData = new NetDataContractSerializer();
using (FileStream fs = new FileStream("NetData.xml", FileMode.Create))
{
netData.Serialize(fs, name);
}
}
Here is the serialized stream. It contains the type information, so there is no need for specifying the type while doing the deserialization.
<Name z:Id="1" z:Type="HelloIndigo.Name" z:Assembly="HelloIndigo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
xmlns="https://schemas.datacontract.org/2004/07/HelloIndigo"
xmlns:i="https://www.w3.org/2001/XMLSchema-instance"
xmlns:z="https://schemas.microsoft.com/2003/10/Serialization/">
<firstName z:Id="2">John</firstName>
<lastName z:Id="3">last</lastName>
</Name>
Following are some precautions that we have to take while doing the serilalization:
- When serializing types that use inheritance, all the types in the hierarchy should be serializable either via the DataContract or the Serializable attribute.
- WCF also provide a KnowType attribute which can be used to substitute the type which serializers will recognize at runtime. This lets us specify the Serializable attribute to some base type and send some derived type.
- We can use IDataContractSurrogate in case we find that there is some property which is not serializable. We can implement this interface, and the serializer will call it while doing the serialization.
Conclusion:
Windows Communication Foundation takes care of everything, but it is important to understand how each serializer works and which one is the best on a particular scenario. You should use DataContractSerializer whenever possible in WCF. It provides interoperability. NetDataContractSerializer should be used when you need type fidelity across the wire. You should user XmlSerializer when you need backwards compatibility with ASMX pages, or you are starting with the existing types which don’t follow the DataContract rules.
Comments
- Anonymous
May 08, 2009
PingBack from http://asp-net-hosting.simplynetdev.com/serialization-in-windows-communication-foundation/