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"%>

Thursday, February 18, 2010

Passing that Bear

I passed (barely) my first Microsoft Certification exam this week. Reading Derik Whittaker’s blog post on the course did offer a warning of the difficulty of what expect. He also talked about XML Serialization and presented a good DimeCast on the subject. As different as .NET was from the previous C++_ and COM technologies, it gradually proved to provide good backward compatibility. So I was kind of looking forward to having a “Core .NET”exam under my belt. When you look real close you see that having a single broad-based .NET exam is really unmanageable.There are simply too many areas to cover.The exam also becomes somewhat of an API bee that intellisense can easily answer once you are familiar with a particular area of the framework.

I know the exam has been around for a while but in 2010, with .NET 4.0 around the corner the exam seems pretty dated. The emphasis on Code Access Security is particularly hard to prepare for, in light of .NET 4.0s make CAS obsolete. Localization was a topic of no applicability for me, but I can accept its inclusion. Using COM libraries from .NET remains useful but bothering to master exposing .NET code for COM consumption doesn’t seem essential to me.

Another bone I had to pick with the exam was coverage of some of the more obscure extensibility points for .NET.Building a Custom CultureInfo class for localization is too esoteric. As valuable as the topic category of reflection is, I question the general need to create assemblies on the fly, such as using the AssemblyBuilder class. If one does need to create code or even assemblies on the fly, there are much better regarded APIs available outside the FCL, such as Cecil.

Another topic covered that I have to question was the introduction to GDI+.The coverage was just a drop in the bucket of possible coverage.I would suggest excluding this topic.This especially true since GDI+ is officially not supported with ASP.NET.I actually wish GDI+ were supported for ASP.NET, particularly for its very useful image conversion functionality. Scott Hanselman just posted on his successful usage of GDI+, even though it is unsupported for ASP.NET. Hopefully he can find out why it remains unsupported!