7

I'm attempting to add a custom header to all SOAP requests over WCF. I found this fantastic article on how to do exactly this. My MessageHeader class looks like this:

public class OperatorNameMessageHeader : MessageHeader
{
    private string opName;

    public const string HeaderName = "OperatorNameMessageHeader";
    public const string HeaderNamespace = "http://schemas.microsoft.com/scout";

    public override string Name { get { return HeaderName; } }
    public override string Namespace { get { return HeaderNamespace; } }

    public string OperatorName
    {
        get { return opName; }
        set { opName = value; }
    }

    public OperatorNameMessageHeader()
    {
    }

    public OperatorNameMessageHeader(string operatorName)
    {
        opName = operatorName;
    }

    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        writer.WriteElementString("OperatorName", opName);
    }
}

One thing the article does not say is how to read the value on the server. According to this post, you can use OperationContext.Current.IncomingMessageHeaders to read these headers. When I look at these MessageHeaders under the debugger, I see 3 headers including my custom one. So, it's definitely showing up in the SOAP data. However, when I call GetHeader:

OperatorNameMessageHeader test = msgHeaders.GetHeader<OperatorNameMessageHeader>(OperatorNameMessageHeader.HeaderName, OperatorNameMessageHeader.HeaderNamespace);

Then test.OperatorName is null. Basically, I'm just getting back an empty OperatorNameMessageHeader object that hasn't been deserialized from the data in the SOAP.

My next step was to run the WCF tracing tool. When I do this, I can verify the custom header is indeed being sent across the wire:

<MessageHeaders>
   <ActivityId CorrelationId="66a7c5b6-3548-4f3c-9120-4484af76b64b" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">f9bef03b-4e7b-4e84-b327-5e79814d9933</ActivityId>
   <OperatorNameMessageHeader xmlns="http://schemas.microsoft.com/scout">
      <OperatorName>Correct Operator Name</OperatorName>
   </OperatorNameMessageHeader>
   <To d4p1:mustUnderstand="1" xmlns:d4p1="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://localhost:90/IRolesAndResourcesManager</To>
   <Action d4p1:mustUnderstand="1" xmlns:d4p1="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IRolesAndResourcesManager/Authenticate</Action>
</MessageHeaders>

So, the server has the data, I just can't get to it. What's the solution to this problem?

Community
  • 1
  • 1
Mike Christensen
  • 88,082
  • 50
  • 208
  • 326
  • I'm having this issue at the moment, Did you ever find a solution? – Declan Feb 18 '15 at 09:24
  • @Declan - Never did! I ended up figuring out a way to do the same thing using an HTTP header though. The information is not in SOAP, and only works with HTTP, so that might be a deal breaker for some. – Mike Christensen Feb 18 '15 at 18:04
  • 2
    @MikeChristensen I'm having this same issue. you should put a bounty on this question :) – theB3RV Mar 10 '16 at 13:31

2 Answers2

1

I had the exact same problem, and was able to make it work as follows:

[DataContract(Namespace = OperatorNameMessageHeader.HeaderNamespace)]
public class OperatorNameMessageHeader
{
    public const string HeaderName = "OperatorNameMessageHeader";
    public const string HeaderNamespace = "http://schemas.microsoft.com/scout";

    [DataMember]
    public string OperatorName { get; set; }
}

With that, I can read the header as follows:

public static OperatorNameMessageHeader DeserializeSoap(string xml)
{
    using (var reader = XmlReader.Create(new StringReader(xml)))
    {
        var m = System.ServiceModel.Channels.Message.CreateMessage(reader, int.MaxValue, MessageVersion.Soap11);
        var operatorNameHeader = m.Headers.GetHeader<OperatorNameMessageHeader>(OperatorNameMessageHeader.HeaderName, OperatorNameMessageHeader.HeaderNamespace);

        return operatorNameHeader;
    }
}

Notice I'm using WCF to deserialize the XML string and therefore needed to use the [DataContract] and [DataMember] attributes - without them, it won't work. Not sure if you need to actually derive from MessageHeader for your case, but for me that isn't necessary in order to read custom headers.

Hope this helps.

Sipke Schoorstra
  • 3,074
  • 1
  • 20
  • 28
  • Your suggestion helped me. However for some reason I was only able to deserialize 1 property from the soap header. In order to deserialize 3 properties I had to split my header definition in 3 parts and deserialize it 3 times. Not sure why. – Anubis Sep 03 '20 at 08:36
0

I had a similar problem. I have to read a username annd password from headers. I found a temporal solution, I'm using XmlDictionaryReader. But with this code I only look for the names, I still can improve it but works for the moment. I have it for VB, will be something similar for C#

        Dim username As String = ""
        Dim password As String = ""
        Dim usernameTokenId As String = ""
        Dim passwordType As String = ""

        For i As Integer = 0 To OperationContext.Current.IncomingMessageHeaders.Count - 1
            Dim mhi As Channels.MessageHeaderInfo = OperationContext.Current.IncomingMessageHeaders.Item(i)
            Dim headers As Channels.MessageHeaders = OperationContext.Current.RequestContext.RequestMessage.Headers
            If mhi.Name.Equals("Security") Then
                Dim xr As XmlDictionaryReader = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i)
                xr.MoveToContent()
                While xr.MoveToNextAttribute()
                    Console.Write(" {0}='{1}'", xr.Name, xr.Value)
                End While
                Do
                    Select Case xr.NodeType
                        Case XmlNodeType.Element
                            If xr.LocalName.Equals("Username") Then
                                username = xr.ReadElementContentAsString()
                            End If
                            If xr.LocalName.Equals("Password") Then
                                password = xr.ReadElementContentAsString()
                            End If

                            While xr.MoveToNextAttribute()
                                If xr.LocalName.Equals("Id") Then
                                    usernameTokenId = xr.Value
                                End If
                                If xr.LocalName.Equals("Type") Then
                                    passwordType = xr.Value
                                End If
                            End While
                        Case XmlNodeType.Attribute

                            'Case XmlNodeType.Text
                            '    Console.Write(xr.Value)
                            'Case XmlNodeType.EndElement
                            '    Console.Write("</{0}>", xr.Name)
                    End Select
                Loop While xr.Read()
            End If


            Dim name As String = mhi.Name
        Next