How to Correctly Retrieve the Value of MessageHeaders in WCF MessageInspector

Problem Description

A WCF message inspectors pass a custom message header from the client caller to the WCF service hosted in a Windows service over TCP.

  • A ClientMessageInspector adds in the custom header. 
  • A DispatchMessageInspector looks for the custom header and interrogates it if it is present.
  • The DispatchMessageInspector looks for the custom header and then calls ToString() on it to get its contents. 

The service WcfMyIncomingMessageInspector : IDispatchMessageInspector calls the WcfMyRequestSoapHeader(System.ServiceModel.Channels.Message message) constructor.

internal WcfMyRequestSoapHeader(System.ServiceModel.Channels.Message message)
{
    foreach (System.ServiceModel.Channels.MessageHeaderInfo header in message.Headers)
    {
        if (header.Name == "MyRequestSoapHeader")
        {
            String documentToLoad = String.Empty;
            try
            {
                XmlDocument document = new XmlDocument();
                documentToLoad = @“<?xml version='1.0' ?>” + header.ToString();
                document.LoadXml(documentToLoad);
                foreach (System.Xml.XmlNode childNode in document.DocumentElement.ChildNodes)
                {
                    WcfUtility.LoadContextElements(this.LoggerContextElements, childNode);
               }
            }
       }
}

The header contents retrieved should look something like:

<MyRequestSoapHeader xmlns="MyRequestSoapHeader">
        <LoggerContextElements>
                <entry name="LocalInstanceId" value="b0b31130-99e4-4fe8-a976-cc39d81c9c7b"></entry>
        </LoggerContextElements>
</MyRequestSoapHeader>

However the string “System.ServiceModel.Channels.HeaderInfoCache+HeaderInfo” is returned from the ToString() method. Next when LoadXml tries to load <?xml version='1.0' ?>System.ServiceModel.Channels.HeaderInfoCache+HeaderInfo and it fails because of the invalid XML.

<Message>Data at the root level is invalid. Line 1, position 23.</Message>

Cause

The reason that “System.ServiceModel.Channels.HeaderInfoCache+HeaderInfo” is returned from the ToString() method is because the type of ‘header’ is System.ServiceModel.Channels.HeaderInfoCache+HeaderInfo instead of System.ServiceModel.Channels.MessageHeader as expected. There is no ToString override in System.ServiceModel.Channels.HeaderInfoCache+HeaderInfo so the type name itself is returned. The problem with this is that MessageHeaders is a collection of MessageHeaderInfo and not of MessageHeader. It is not a good idea to count on objects enumerated by this class to have a particular type other than MessageHeaderInfo, which does not override ToString(). 

MyRequestSoapHeader in this case does derive from MessageHeader, however, since MessageHeader is not serializable, neither is WcfMyRequestSoapHeader, instead this class manages writing itself to the message and so, must manage retrieving itself from the message as well. 

Resolution

Here is a changed version of the request and response headers that solves the problem. The only change is in the constructor – instead of relying on the type of the header in the MessageHeaderCollection (that is, relying that the type will have an override of the ToString() method), these constructors access the header XML directly.

       internal WcfMyRequestSoapHeader(System.ServiceModel.Channels.Message message)
        {
            StringWriter writer = new StringWriter();
            writer.Write(@“<?xml version='1.0' ?>”);
           try
            {
                message.Headers.WriteHeader(message.Headers.FindHeader(this.Name, this.Namespace), new    XmlTextWriter(writer));
                XmlDocument document = new XmlDocument();
                document.LoadXml(writer.ToString());
                foreach (System.Xml.XmlNode childNode in document.DocumentElement.ChildNodes)
                {
                    WcfUtility.LoadContextElements(this.LoggerContextElements, childNode);
                }
            }

Another alternative would be to define WcfMyRequestSoapHeader using a DataContract and use one of the GetHeader overrides to retrieve the header as the appropriate type – this would simplify the code a good deal and remove the need for parsing the header contents as an XmlDocument.