Paolo Pialorsi - Bridge The Gap!

Living in a Service Oriented World

Handling custom SOAP headers via WCF Behaviors

A few days ago a customer of mine asked me how to define a WCF behavior to add a custom SOAP Header to sent/received messages.

The solution is not so far from what I've shown in the previous "Writing a WCF Message Inspector" post. In fact one way of working is to define a custom message inspector that writes/reads the custom SOAP Header.

So first of all we need a SOAP Header. Here is the code to define a custom header to handle a random key (as a Guid) injected in every request sent from the consumer to the service:

public class CustomHeader : MessageHeader
{
    private String _key;

    public String Key
    {
        get
        {
            return (this._key);
        }
    }

    public CustomHeader(String key)
    {
        this._key = key;
    }

    public override string Name
    {
        get { return (CustomHeaderNames.CustomHeaderName); }
    }

    public override string Namespace
    {
        get { return (CustomHeaderNames.CustomHeaderNamespace); }
    }

    protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        // Write the content of the header directly using the XmlDictionaryWriter
        writer.WriteElementString(CustomHeaderNames.KeyName, this.Key);
    }

    public static CustomHeader ReadHeader(XmlDictionaryReader reader)
    {
        // Read the header content (key) using the XmlDictionaryReader
        if (reader.ReadToDescendant(CustomHeaderNames.KeyName, CustomHeaderNames.CustomHeaderNamespace))
        {
            String key = reader.ReadElementString();
            return (new CustomHeader(key));
        }
        else
        {
            return null;
        }
    }

}

public static class CustomHeaderNames
{
    public const String CustomHeaderName = "CustomHeader";

    public const String KeyName = "Key";

    public const String CustomHeaderNamespace = "http://schemas.devleap.com/CustomHeader";

}

As you can see it is a type inheriting from MessageHeader class. Notice the OnWriteHeaderContents override, which is invoked by WCF infrastructure to serialize the SOAP Header, and the ReadHeader static method that we will use later.

Such a SOAP Header need to be added by the consumer and read by the service. To do this we need a MessageInspector like the following one:

public class CustomMessageInspector : IDispatchMessageInspector, IClientMessageInspector
{
    #region Message Inspector of the Service

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        // Look for my custom header in the request
        Int32 headerPosition = request.Headers.FindHeader(CustomHeaderNames.CustomHeaderName, CustomHeaderNames.CustomHeaderNamespace);

        // Get an XmlDictionaryReader to read the header content
        XmlDictionaryReader reader =
request.Headers.GetReaderAtHeader(headerPosition);

        // Read it through its static method ReadHeader
        CustomHeader header = CustomHeader.ReadHeader(reader);

        // Add the content of the header to the IncomingMessageProperties dictionary
      
OperationContext.Current.IncomingMessageProperties.Add("key", header.Key);

        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
    }

    #endregion

    #region Message Inspector of the Consumer

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        // Prepare the request message copy to be modified
        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
        request = buffer.CreateMessage();

        // Simulate to have a random Key generation process
        request.Headers.Add(new CustomHeader(Guid.NewGuid().ToString()));

        return null;
    }

    #endregion
}

As you can see from the code sample above, we use the IClientMessageInspector implementation to handle the addition of the header in the consumer-side code, while we use the IDispatchMessageInspector on the service side, to extract the header. It is interesting the FindHeader method of the MessageHeaders collection, as well as the method GetReaderAtHeader, provided by the same collection of SOAP Headers. The result of this last method is an XmlDictionaryReader that we use to read our custom header content, through the ReadHeader static method we've already introduced.

The service will be able to read the Key provided throught the custom SOAP header simply querying the IncomingMessageProperties dictionary:

OperationContext.Current.IncomingMessageProperties["key"]

Of course this custom MessageInspector needs to be plugged into the WCF pipeline using a custom behavior like the following one:

[AttributeUsage(AttributeTargets.Class)]
public class CustomBehavior : Attribute, IEndpointBehavior
{
    #region IEndpointBehavior Members

    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        CustomMessageInspector inspector = new CustomMessageInspector();
        clientRuntime.MessageInspectors.Add(inspector);
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    {
        ChannelDispatcher channelDispatcher = endpointDispatcher.ChannelDispatcher;
        if (channelDispatcher != null)
        {
            foreach (EndpointDispatcher ed in channelDispatcher.Endpoints)
            {
                CustomMessageInspector inspector = new CustomMessageInspector();
                ed.DispatchRuntime.MessageInspectors.Add(inspector);
            }
        }
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    #endregion
}

We also need an ExtensionElement to configure the behavior:

public class CustomBehaviorExtensionElement : BehaviorExtensionElement
{
    protected override object CreateBehavior()
    {
        return new CustomBehavior();
    }

    public override Type BehaviorType
    {
        get
        {
            return typeof(CustomBehavior);
        }
    }
}

At last we can configure in the .config file of our service and consumer the behavior. Here is the service side configuration:

<system.serviceModel>

  <extensions>
    <behaviorExtensions>
      <add name="customBehavior" type="DevLeap.WCF.Behaviors.Extensions.CustomBehaviorExtensionElement, DevLeap.WCF.Behaviors.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </behaviorExtensions>
  </extensions>

<services>
  <service name="DevLeap.WCF.Behaviors.Services.ServiceOne" behaviorConfiguration="serviceBehavior">
    <endpoint
     address="net.tcp://localhost:35001/ServiceOne/"
     binding="netTcpBinding"
     contract="DevLeap.WCF.Behaviors.Contracts.IServiceOne"
    behaviorConfiguration="endpointBehavior" />
  </service>
</services>

<behaviors>
  <endpointBehaviors>
    <behavior name="endpointBehavior">
     
<customBehavior />
    </behavior>
  </endpointBehaviors>

  </behaviors>

</system.serviceModel>

And here is the configuration of the consumer-side:

<system.serviceModel>

    <extensions>
      <behaviorExtensions>
        <add name="customBehavior" type="DevLeap.WCF.Behaviors.Extensions.CustomBehaviorExtensionElement, DevLeap.WCF.Behaviors.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </behaviorExtensions>
    </extensions>

    <client>
            <endpoint name="serviceOneEndpoint"
                address="net.tcp://localhost:35001/ServiceOne/"
                binding="netTcpBinding"
                contract="DevLeap.WCF.Behaviors.Contracts.IServiceOne"
                behaviorConfiguration="serviceOneBehavior" />
        </client>

    <behaviors>
      <endpointBehaviors>
        <behavior name="serviceOneBehavior">
          <customBehavior />
        </behavior>
      </endpointBehaviors>
    </behaviors>

</system.serviceModel>

That's all! Enjoy your custom SOAP header defining infrastructural protocols, but don't forget to check the wide range of WS-* specifications before inventing your own :-) ...

Posted: Feb 25 2008, 11:13 AM by paolopia | with 15 comment(s) |
Filed under: , ,

Comments

Tony said:

Hello Paolo Pialorsi,

Thanks for the excellent piece of code. I have been trying for several days to make this work.

However, I am having bit difficulty in implementing the same using code instead of the configuration file. Do you have any sample code to replace the above config sections?

- Tony

# April 29, 2008 3:05 PM

Breezback said:

Hey,

Any code avail to download?

Please post me update: ilan_levy at msn.com

# May 3, 2008 12:09 PM

paolopia said:

x Tony: you simply need to add your custom behavior via code using the ServiceHost instance and adding it to the Behaviors collection of the Description property.

x Breezback: actually I don't have a download available from here, may be in the future. However following my step by step instructions you could be able to reproduce it.

# May 23, 2008 2:54 AM

Kris-I said:

@Tony John

I'm trying to update you sample by add a database a DAL (library in the host solution) but it's not working fine

# August 25, 2008 10:40 AM

jdanforth said:

Thanks a million, I've been at this all evening. This is worth a blog post :)

# October 23, 2008 2:29 PM

MrAdvani said:

Paolo, This is excellent work, thanks for posting it.

I haven't tried your code as yet, but from going through it I have a question -

By introducing a custom soap header in this way will it be included as part of the WSDL too?

Thanks

Ravi

# November 6, 2009 2:04 PM

Tavi said:

Paolo

Thanks for excellent post

Just i want to add an additional element to existing SOAP Header...like clientID

Is it possible?

If possible where exactly i need to change in web.config file?

# December 23, 2009 6:18 AM

Stephen said:

Any way to add the header without IClientMessageInspector and IEndpointBehavior?  Compact Framework does not have those....

# January 13, 2010 5:14 PM

Adam said:

This is a great article.  Very clear, but I'd really like to see an example implements the IContractBehavior instead of IEndpointBehavior.  All the example I can find use the EndpointBehavior.  The problem is that you can't attach the ContractBehavior in configuration, and I'm having a hard time knowing exactly how to add the behavior to the behaviors collection.

My situation is that I already have a custom header defined on the Service, so I don't think I can inherit from IContractBehaviorAttribute and simply decorate with an attribute?  Tell me if I'm wrong.  What are my options in this situation.  I only want the header added on one particular contract and not everything in the endpoint.

Thanks,

Adam

# April 23, 2010 6:26 PM

Ganesh said:

Thanks Tony and Paolo :

Your article and sample helped me a lot today. I was looking for exactly the same functionality!!

# September 14, 2010 9:28 AM

dror said:

I tougth its working , but now I think is not working sample.

<service name="DevLeap.WCF.Behaviors.Services.ServiceOne" behaviorConfiguration="serviceBehavior">

is not legal.

I remove it, but still , my custom behavior on the server is not invoked.

# October 20, 2010 7:55 AM