Tuesday, March 2, 2010

Making a case for the NetDataContractSerializer

Despite Microsoft’s best effort to keep it a secret, the shared type contracts in the NetDataContractSerializer does have its uses, primarily internally to an application that needs to distribute itself to handle its load. WCF has distanced itself from the code-first (as opposed to schema-first) style used in ASMX web services and .NET remoting. In many cases, I concur with the arguments against the code-first approach. For dealing with other applications, even within an enterprise, it is a much cleaner design to expose an interface. The quality of the WSDL can be much better, allowing service consumers to have a quicker, less buggy development. An SOA approach to recognizing opportunities to allow asynchronous processing is definitely a better architecture to deal with load distribution, than a shared type, code first solution.


Here is a scenario that I have used to quickly make use of the NetDataContractSerializer. I have an application that exposes a web interface but is also heavily involved in EAI (enterprise application integration) scenarios. It was helpful to be able to process messages from a queue and have them processed in a scalable way using IIS and NLB (network load balancing). The open-source tool AutoMapper has opened my mind to using an interface with separate types on the client and server sides. AutoMapper takes away a lot of the grunt work in dealing with separate types that have many identically named and typed fields. Nevertheless, it is very convenient to use a common assembly and a shared type between the client and server.


To use the NetDataContractSerializer you must include in both client and server the source file for the NetDataContract attribute. Aaron Skonnard has a nice reference with the relevant source code. If you use his version just note that the name of the attribute is NetDataContractFormat whereas I chose use the name NetDataContract. Here is a VB.NET version of this:


Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports System.ServiceModel.Dispatcher
Imports System.ServiceModel.Channels
Imports System.Runtime.Serialization
Imports System.Xml

Public Class NetDataContractAttribute
Public Class NetDataContract
Inherits Attribute
Implements IOperationBehavior

Public Sub AddBindingParameters(ByVal description As OperationDescription, ByVal parameters As BindingParameterCollection) Implements IOperationBehavior.AddBindingParameters

End Sub


Public Sub ApplyClientBehavior(ByVal description As OperationDescription, ByVal proxy As ClientOperation) Implements IOperationBehavior.ApplyClientBehavior
ReplaceDataContractSerializerOperationBehavior(description)
End Sub


Public Sub ApplyDispatchBehavior(ByVal description As OperationDescription, ByVal dispatch As DispatchOperation) Implements IOperationBehavior.ApplyDispatchBehavior
ReplaceDataContractSerializerOperationBehavior(description)
End Sub

Public Sub Validate(ByVal description As OperationDescription) Implements IOperationBehavior.Validate
End Sub


Private Shared Sub ReplaceDataContractSerializerOperationBehavior(ByVal description As OperationDescription)
Dim dcsOperationBehavior As DataContractSerializerOperationBehavior = description.Behaviors.Find(Of DataContractSerializerOperationBehavior)()

If dcsOperationBehavior IsNot Nothing Then
description.Behaviors.Remove(dcsOperationBehavior)
description.Behaviors.Add(New NetDataContractSerializerOperationBehavior(description))
End If
End Sub

Public Class NetDataContractSerializerOperationBehavior
Inherits DataContractSerializerOperationBehavior
Public Sub New(ByVal operationDescription As OperationDescription)
MyBase.New(operationDescription)
End Sub

Public Overrides Function CreateSerializer(ByVal type As Type, ByVal name As String, ByVal ns As String, ByVal knownTypes As IList(Of Type)) As XmlObjectSerializer
Return New NetDataContractSerializer()
End Function
Public Overrides Function CreateSerializer(ByVal type As Type, ByVal name As XmlDictionaryString, ByVal ns As XmlDictionaryString, ByVal knownTypes As IList(Of Type)) As XmlObjectSerializer
Return New NetDataContractSerializer()
End Function
End Class
End Class

End Class


The NetDataContractSerializer provides shared type information by including assembly information in the serialized message. I use this simple LocationInfo class in an assembly called SharedType.dll that isn’t strongly named to illustrate the differences in how the XML becomes serialized.

 Imports System.Runtime.Serialization  
<DataContract()> _
Public Class LocationInfo
<DataMember()> _
Public postalCode As String
<DataMember()> _
Public latitude As Double
<DataMember()> _
Public longitude As Double
End Class


My Service Contract was based on this interface.
 <ServiceContract()> _  
Public Interface INetDataContractSerializerSample
<NetDataContract()> _
<OperationContract()> _
Sub DetermineCoordinates(ByRef loc As LocationInfo)
End Interface


The default DataContractSerializer data looks like this:


 <s:Body>  
<DetermineCoordinates xmlns="http://tempuri.org/">
<loc xmlns:a="http://schemas.datacontract.org/2004/07/SharedTypes" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:latitude>0</a:latitude>
<a:longitude>0</a:longitude>
<a:postalCode>90125</a:postalCode>
</loc>
</DetermineCoordinates>
</s:Body>

The NetDataContractSerializer data adds type information based on the shared type in the assembly. The type data includes the assembly name, assembly version, culture, and public key token.


 <s:Body>  
<DetermineCoordinates xmlns="http://tempuri.org/">
<LocationInfo z:Id="1" z:Type="SharedTypes.LocationInfo" z:Assembly="SharedTypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="http://schemas.datacontract.org/2004/07/SharedTypes" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<latitude>0</latitude>
<longitude>0</longitude>
<postalCode z:Id="2">90125</postalCode>
</LocationInfo>
</DetermineCoordinates>
</s:Body>


One gotcha I ran into in using the NetDataContractSerializer is the client not supplying type information. This results in a Protocol exception indicating that the data “does not contain expected attribute 'http://schemas.microsoft.com/2003/10/Serialization/:Type'.”. If you encounter this error, double check the client proxy file (by default reference.vb or reference.cs) in the Service References for this directory. The desired Operation Contract should have the NetDataContract attribute applied.

No comments:

Post a Comment