Showing posts with label WCF. Show all posts
Showing posts with label WCF. Show all posts

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.

Thursday, February 25, 2010

WCF Zero-Config in .NET 3.5! (Part II)

In this blog post I complete the process of creating a custom WCF service host to allow IIS service hosting without a config file. In my prior post, I create mex endpoints in IIS without adding information in the web.config. The custom host created earlier will be extended in this post to create an endpoint using a WsHttpBinding. This approach has been tested only on IIS7 and .NET framework 3.5 SP1. The technique applies regardless of whether the ServiceContract attribute has been applied to an interface or a class.


First, lets examine the easier case of a class with the ServiceContract attribute applied. We start with the ABCs: address, binding, and contract. IIS (and the Cassini debugging host) provide the address and the class that implements the service to the ServiceHost constructor. This class is directly used as the contract. As in the prior post, I have modified the service .svc file to use the custom ServiceHostFactory we have created. The Custom ServiceHostFactory calls our custom ServiceHost, ZeroConfigServiceHost.


That was easy enough, but dealing with interfaces that have the ServiceContract attribute applied is trickier. The class implementing a given service is known but in order to provide the contract we need to determine the correct interface implemented by this class. I use reflection to identify this information.


I begin this identification by enumerating all interfaces in the current assembly to see if they have the ServiceContract attribute applied. I build a list containing all of these interface types. I then take the type of the class and find which ServiceContract it implements. I use the IsAssignableFrom method available to a System.Type object to match the class to its interface. Once the contract interface is identified it can be used to provide the contract for the endpoint. Everything else is the same as if the ServiceContract was directly applied to a class.


Source File 1: Reflection class
 Imports System.Reflection  
Public Class ReflectionExperiment
Dim oServiceInterfaceTypes As New List(Of Type)
Public Sub New()
Dim oAssembly As Assembly
oAssembly = Assembly.GetExecutingAssembly()
oServiceInterfaceTypes = FindServiceInterfaces(oAssembly)
End Sub
Public Function FindServiceInterfaces(ByVal oAssembly As Assembly) As List(Of Type)
Dim ExportedTypes() As Type
Dim currType As Type
Dim olServiceInterfaceTypes As New List(Of Type)
ExportedTypes = oAssembly.GetExportedTypes()
For Each currType In ExportedTypes
If currType.IsInterface = True AndAlso IsTypeAServiceContract(currType) Then
olServiceInterfaceTypes.Add(currType)
End If
Next
Return olServiceInterfaceTypes
End Function
Public Function IsTypeAServiceContract(ByVal oType As Type) As Boolean
Dim oServiceContractAttribute As New System.ServiceModel.ServiceContractAttribute
Return Attribute.IsDefined(oType, oServiceContractAttribute.GetType())
End Function
Public Function FindServiceContractType(ByVal serviceType As Type, ByRef serviceContractType As Type) As Boolean
Dim bRetVal As Boolean = False
Dim currType As Type
If IsTypeAServiceContract(serviceType) Then
serviceContractType = serviceType
Return True
End If
For Each currType In oServiceInterfaceTypes
If currType.IsAssignableFrom(serviceType) Then
bRetVal = True
serviceContractType = currType
Exit For
End If
Next
Return bRetVal
End Function
End Class


File 2: ServiceHost-related Classes
 Imports System.ServiceModel.Description  
Imports System.ServiceModel.Activation
Public Class ZeroConfigServiceHost
Inherits ServiceHost
Private pReflectionExperiment As New ReflectionExperiment
Public Sub New()
MyBase.New()
AddMexEndpoint()
End Sub
Public Sub AddEndpoints (ByVal serviceType As Type, ByVal ParamArray baseAddresses As Uri())
Dim serviceContractType As Type
Dim oReflectionExperiment As New ReflectionExperiment
AddMexEndpoint()
If oReflectionExperiment.FindServiceContractType (serviceType, serviceContractType) Then
Me.AddServiceEndpoint (serviceContractType, New WSHttpBinding, baseAddresses (0).AbsoluteUri)
End If
End Sub
Public Sub AddMexEndpoint()
Dim mb As ServiceMetadataBehavior
mb = Me.Description.Behaviors.Find (Of ServiceMetadataBehavior)()
If (mb Is Nothing) Then
mb = New ServiceMetadataBehavior()
mb.HttpGetEnabled = True
Me.Description.Behaviors.Add (mb)
Me.AddServiceEndpoint (ServiceMetadataBehavior.MexContractName, _
MetadataExchangeBindings.CreateMexHttpBinding(), _
"mex")
End If
End Sub
Public Sub New (ByVal serviceType As Type, ByVal ParamArray baseAddresses As Uri())
MyBase.New (serviceType, baseAddresses)
AddEndpoints (serviceType, baseAddresses)
End Sub
Public Sub New (ByVal singletonInstance As Object, ByVal ParamArray baseAddresses As Uri())
MyBase.New (singletonInstance, baseAddresses)
AddMexEndpoint()
End Sub
End Class
Public NotInheritable Class ZeroConfigServiceHostFactory
Inherits ServiceHostFactory
Public Overrides Function CreateServiceHost (ByVal constructorString As String, ByVal baseAddresses As Uri()) _
As ServiceHostBase
Return MyBase.CreateServiceHost (constructorString, baseAddresses)
End Function
Protected Overrides Function CreateServiceHost (ByVal serviceType As Type, ByVal baseAddresses As Uri()) _
As ServiceHost
Return New ZeroConfigServiceHost (serviceType, baseAddresses)
End Function
End Class

Monday, February 22, 2010

WCF Zero-Config in .NET 3.5! (Part I)

WCF configuration is, in my mind, the biggest misstep made within WCF (prior to .NET 4.0), compared to ASMX web services. For someone getting starting with web services, their first steps should “just work”. My early successes with ASMX web services allowed me to build confidence. WCF, on the other hand, forces one to immediately deal with System.ServiceModel XML configuration hell. This can scare developers away, especially with the incredibly configurable and extensible WCF. Fortunately .NET 4.0 does allow zero-config WCF, but what about .NET 3.5? This post shows part of how to accomplish this within ASP.NET.



I don’t want to advocate config-less use of WCF in production, but it is sure convenient to have this available in testing. When I first worked with WCF, I was not aware of the WCF Service Configuration Tool (SvcConfigEditor.exe) which has a GUI to more safely edit the System.ServiceModel part of a config file. This caused a lot of teeth-gnashing indeed.


The first big step to making WCF just work was POCO (plain old CLR object) support added to .NET 3.5 SP1. This brings back the ASMX default serialization of including all public fields and properties in a class for serialization. In WCF before 3.5 SP1, a class would have the DataContract attribute applied to it.


In this post I explain how to use a custom ServiceHost and ServiceHostFactory to automatically add HTTP-based metadata exchange (MEX) support for any given service hosted within ASP.NET. This code is reuseable, requiring only the Factory attribute to be added to the service directive line in the .svc. The .svc Factory refers to a custom service host factory. The custom factory simply has code to reference the custom ServiceHost.


The ServiceHost class inherits from the default ServiceHost class. It calls the base class for all functionality and then adds the mex-specific logic. Identifying which extensibility hook is needed is the hard part. From there it simply a matter of mirroring the config file entries to enable mex with programmatic setup. As with the config file, a behavior must be added to set HttpGetEnabled = “true” and add a Mex Endpoint to the service. Note that this example covers HTTP based metadata exchange, not HTTPS, although the concepts are similar.



Step 1: Add the ServiceHost and ServiceHostFactory to a new file within your web site.
 Imports System.Collections  
Imports System.Collections.Generic
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Description
Imports System.ServiceModel.Dispatcher
Imports System.Xml.Schema
Imports ServiceDescription = System.Web.Services.Description.ServiceDescription
Imports System.ServiceModel.Activation
Public Class AutoMexServiceHost
Inherits ServiceHost
Public Sub New()
ConditionallyAddMexEndpoint()
End Sub
Public Sub ConditionallyAddMexEndpoint()
Dim mb As ServiceMetadataBehavior
mb = Me.Description.Behaviors.Find(Of ServiceMetadataBehavior)()
If (mb Is Nothing) Then
mb = New ServiceMetadataBehavior()
mb.HttpGetEnabled = True
Me.Description.Behaviors.Add(mb)
Me.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, _
MetadataExchangeBindings.CreateMexHttpBinding(), _
"mex")
End If
End Sub
Public Sub New(ByVal serviceType As Type, ByVal ParamArray baseAddresses As Uri())
MyBase.New(serviceType, baseAddresses)
ConditionallyAddMexEndpoint()
End Sub
Public Sub New(ByVal singletonInstance As Object, ByVal ParamArray baseAddresses As Uri())
MyBase.New(singletonInstance, baseAddresses)
ConditionallyAddMexEndpoint()
End Sub
End Class
Public NotInheritable Class AutoMexServiceHostFactory
Inherits ServiceHostFactory
Public Overrides Function CreateServiceHost(ByVal constructorString As String, ByVal baseAddresses As Uri()) As ServiceHostBase
Return MyBase.CreateServiceHost(constructorString, baseAddresses)
End Function
Protected Overrides Function CreateServiceHost(ByVal serviceType As Type, ByVal baseAddresses As Uri()) As ServiceHost
Return New AutoMexServiceHost(serviceType, baseAddresses)
End Function
End Class


Step 2: Add Factory to your existing .svc file
For example,


<%@ ServiceHost Language="VB" Debug="true" Service="WcfNoodler.Service1" CodeBehind="Service1.svc.vb" Factory="WcfNoodler.AutoMexServiceHostFactory"%>