Exposing orchestrations with WCF and headers

[There where still people at work back in Holland, while I was visiting LA to attend the PDC . One who stayed home made a really great solution for the BizTalk WCF Adapter and WSDL Headers. To interesting solution to keep it offline. So, here is a guest post from Ronald Kuijpers. LinkedIn-profile]

Recently I was asked to expose an orchestration (or its schemas) using WCF. Due to company standards, the WCF service had to use a custom header for inbound and outbound messages. However, the orchestration did not access the header, it just had to be copied from inbound to outbound.

The problem is that the WCF Publishing Wizard does not support headers, as can be read on msdn:

The BizTalk WCF Service Publishing Wizard does not include custom SOAP header definitions in the generated metadata. To publish metadata for WCF services using custom SOAP headers, you should manually create a Web Services Description Language (WSDL) file. You can use the externalMetadataLocation attribute of the <serviceMetadata> element in the Web.config file that the wizard generates to specify the location of the WSDL file. The WSDL file is returned to the user in response to WSDL and metadata exchange (MEX) requests instead of the auto-generated WSDL.

Luckily, also the answer is presented… but I don’t like to create a wsdl manually. Several other sources show how to change the generation of the wsdl. For me, the most important were written by Tomas Restrepo [here and here] and Patrick Wellink [here], but none added a header.

For adding the header messages, I dug into the System.ServiceModel.dll.

The second thing I wanted, going to create my own EndpointBehavior anyway, was to copy the header from the inbound to the outbound message. This way, I could concentrate in my orchestration on things the matter. This solution fits very well into the WCF architecture. Some nice blogsposts about message inspectors were written by Poalo Pialorsi [here and here].

First, create a class that derives from BehaviorExtensionElement and implements IWsdlExportExtension and IEndpointBehavior. You have to derive from BehaviorExtensionElement to make the component configurable, implement IWsdlExportExtension to change the generated wsdl and implement IEndpointBehavior for copying the header.

   1: public class CustomHeaderEndpointBehavior : BehaviorExtensionElement, IWsdlExportExtension, IEndpointBehavior
   2:     {
   3:         #region BehaviorExtensionElement Overrides
   4:         public override Type BehaviorType
   5:         {
   6:             get
   7:             {
   8:                 return typeof(CustomHeaderEndpointBehavior);
   9:             }
  10:         }
  12:         protected override object CreateBehavior()
  13:         {
  14:             return new CustomHeaderEndpointBehavior();
  15:         } 
  16:         #endregion
  18:         #region IEndpointBehavior Members
  22:         public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  23:         {
  24:             CustomHeaderMessageInspector headerInspector = new CustomHeaderMessageInspector();
  25:             endpointDispatcher.DispatchRuntime.MessageInspectors.Add(headerInspector);
  26:         }
  30:         #endregion
  32:         #region IWsdlExportExtension Members
  36:         public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
  37:         {
  38:             CustomerHeaderWsdlExport.ExportEndpoint(exporter, context);
  39:         }
  41:         #endregion
  42:     }

Note that only a few interface methods have to be implemented. Copying the header is taken care of by the CustomHeaderMessageInspector (credits to Poalo) :

   1: public class CustomHeaderMessageInspector : IDispatchMessageInspector
   2:     {
   3:         #region Message Inspector of the Service
   7:         public void BeforeSendReply(ref Message reply, object correlationState)
   8:         {
   9:             // Look for my custom header in the request
  10:             Int32 headerPosition = OperationContext.Current.IncomingMessageHeaders.FindHeader(CustomHeaderNames.CustomHeaderName, CustomHeaderNames.CustomHeaderNamespace);
  12:             // Get an XmlDictionaryReader to read the header content
  13:             XmlDictionaryReader reader = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(headerPosition);
  15:             // Read through its static method ReadHeader
  16:             CustomHeader header = CustomHeader.ReadHeader(reader);
  18:             if (header != null)
  19:             {
  20:                 // Add the header from the request
  21:                 reply.Headers.Add(header);
  22:             }
  23:         }
  25:         #endregion
  26:     } 

Adding the header messages is a bit more complicated:

  1. Add the header schema;
  2. Add the header schema namespace;
  3. Create and add a header message description;
  4. Add header to operation description.

A piece of code says more than a thousand words:

   1: public class CustomerHeaderWsdlExport
   2:     {
   3:         public static void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
   4:         {
   5:             // Read the schema of the custom header message
   6:             XmlSchema customSoapHeaderSchema = XmlSchema.Read(Assembly.GetExecutingAssembly().GetManifestResourceStream("CustomHeaderBehavior.CustomSoapHeader.xsd"), new ValidationEventHandler(CustomerHeaderWsdlExport.ValidationEventHandler));
   8:             // Create the HeaderMessage to add to wsdl:message AND to refer to from wsdl:operation
   9:             System.Web.Services.Description.Message headerMessage = CreateHeaderMessage();
  12:             foreach (WsdlDescription wsdl in exporter.GeneratedWsdlDocuments)
  13:             {
  14:                 // Add the schema of the CustomSoapHeader to the types AND add the namespace to the list of namespaces
  15:                 wsdl.Types.Schemas.Add(customSoapHeaderSchema);
  16:                 wsdl.Namespaces.Add("ptq0", "http://.../CustomSoapHeader/V1");
  18:                 // The actual adding of the message to the list of messages
  19:                 wsdl.Messages.Add(headerMessage);
  20:             }
  22:             addHeaderToOperations(headerMessage, context);
  24:         }
  25: }

The following code generates the header message description:

   1: private static System.Web.Services.Description.Message CreateHeaderMessage()
   2:        {
   3:            // Create Message
   4:            System.Web.Services.Description.Message headerMessage = new System.Web.Services.Description.Message();
   6:            // Set the name of the header message
   7:            headerMessage.Name = "CustomHeader";
   9:            // Create the messagepart and add to the header message
  10:            MessagePart part = new MessagePart();
  11:            part.Name = "Header";
  12:            part.Element = new XmlQualifiedName("CustomSoapHeader", "http://.../CustomSoapHeader/V1");
  13:            headerMessage.Parts.Add(part);
  15:            return headerMessage;
  16:        }

The method addHeaderToOperations adds the header to the input and output message bindings using a SoapHeaderBinding.

   1: private static void addHeaderToOperations(System.Web.Services.Description.Message headerMessage, WsdlEndpointConversionContext context)
   2:         {
   3:             // Create a XmlQualifiedName based on the header message, this will be used for binding the header message and the SoapHeaderBinding
   4:             XmlQualifiedName header = new XmlQualifiedName(headerMessage.Name, headerMessage.ServiceDescription.TargetNamespace);
   6:             foreach (OperationBinding operation in context.WsdlBinding.Operations)
   7:             {
   8:                 // Add the SoapHeaderBinding to the MessageBinding
   9:                 ExportMessageHeaderBinding(operation.Input, context, header, false);
  10:                 ExportMessageHeaderBinding(operation.Output, context, header, false);
  11:             }
  12:         }
  13: private static void ExportMessageHeaderBinding(MessageBinding messageBinding, WsdlEndpointConversionContext context, XmlQualifiedName header, bool isEncoded)
  14:         {
  15:             // For brevity, assume Soap12HeaderBinding for Soap 1.2
  16:             SoapHeaderBinding extension = new Soap12HeaderBinding();
  18:             binding.Part = "Header";
  19:             binding.Message = header;
  20:             binding.Use = isEncoded ? SoapBindingUse.Encoded : SoapBindingUse.Literal;
  22:             messageBinding.Extensions.Add(extension);
  23:         }

This might seem a lot of code, but is almost a full working solution. Patricks blogpost does an excellent job of explaining how to put this to work for BizTalk.

Comments (1) -

Nice post.

Today I want to meet the exact same requirement as you described in your post. Would it be possible to have a complete working sample for download (or mail it to me ?)

It would be a great help.

Add comment

Şarkı Sozleri