Saturday, March 13, 2010

Where I work, all of our internal published web services are proxied using IBM’s DataPower SOA Appliances. One of the requirements for services that want to leverage the infrastructure is that all requests and responses to the service must contain a custom header which contains parameters like “MessageID” for correlation, “SiteID” for location information, etc. For SOAP based services, this information must be transmitted in the SOAP header. For the purposes of this article, let’s say we need to include an object like the following into the SOAP header of every service operation.

Back in the ASMX days adding headers like this to a service method was as easy as adding a SoapHeader attribute to your WebMethod. Simple. In the WCF world, not so simple.

There are 3 issues to be addressed:

We need some way to inject the custom header into the WSDL so the service clients are aware of it and can generate client proxies accordingly.

We need some method to read and process the custom header from the request.

We need some way to roundtrip the header back to the client.

In our case, we’d like to specify whether the custom header should be included at the service contract level, so an IContractBehavior seems like a good place to start. We also know that we’ll be processing the request and reply messages, so we’re going to need an IDispatchMessageInspector too.

Let’s start with the message inspector. The responsibility of this class is to extract the header from incoming messages, store it in the OperationContext in case the service needs to use it (i.e. for logging purposes), and roundtrip it into the reply message.

///<summary>/// Retrieves and stores the custom header from requests and roundtrips it into responses.
///</summary>publicclass CustomHeaderMessageInspector : IDispatchMessageInspector
{
object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
// Grab the header from the request
// TODO: Handle missing or invalid header CustomHeader header = header = request.Headers.GetHeader<CustomHeader>(CustomHeader.Name, CustomHeader.Namespace);
// Stash the header in the OperationContext so the service code can reference it if necessary OperationContext.Current.IncomingMessageProperties[CustomHeader.OperationContextKey] = header;
// The object returned from this method is passed as the "correlationState" parameter to the BeforeSendReply
// method. We can use this to "echo" back the header to the caller.return header;
}
void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
{
// correlationState should be the CustomHeader (passed to this method from AfterReceiveRequest)
// If an exception occured before or during the header processing, this may be null.if (correlationState !=null)
{
// Get the custom header object passed in on the request var headerObject = (CustomHeader)correlationState;
// Build the MessageHeader object from our custom header var headerMessage = MessageHeader.CreateHeader(CustomHeader.Name, CustomHeader.Namespace, headerObject);
// Inject our custom header into the reply. reply.Headers.Add(headerMessage);
}
}
}

Now that we have the inspector, we’ll need to create an attribute and implement IContractBehavior so we can attach this inspector to the desired service contracts.

At this point, the custom header is usable, but with one problem. It’s not defined in the WSDL, so the service clients might not “know” about it. To add the header to the wizard, we just need to implement the ExportContract method of the IWsdlExportExtension interface that we left as a “TODO” in the code above.

Of course this was a very basic implementation for a specific need, but this approach could be extended to create a more “generic attribute” that you could specify header type as a parameter or allow for updating the header values to return in the reply (similar to how the “old school” SoapHeader attribute worked).