WCF message:How to remove the SOAP Header element?

10,668

Solution 1

Option 1: Use bacicHttpBinding, it will not add content to the header (when not configured for security)

Option 2: Implement a custom mesaage encoder and strip the header there. anywhere before that there is a chance wcf will add the header back. See sample encoder here.

Solution 2

That question is a tricky one: let's take it step by step

Some Context

The Message class writes its headers in its ToString() method. ToString() then calls an internal overload ToString(XmlDictionaryWriter writer) which then starts writing:

// System.ServiceModel.Channels.Message
internal void ToString(XmlDictionaryWriter writer)
{
    if (this.IsDisposed)
    {
        throw TraceUtility.ThrowHelperError(this.CreateMessageDisposedException(), this);
    }
    if (this.Version.Envelope != EnvelopeVersion.None)
    {
        this.WriteStartEnvelope(writer);
        this.WriteStartHeaders(writer);
        MessageHeaders headers = this.Headers;
        for (int i = 0; i < headers.Count; i++)
        {
            headers.WriteHeader(i, writer);
        }
        writer.WriteEndElement();
        MessageDictionary arg_60_0 = XD.MessageDictionary;
        this.WriteStartBody(writer);
    }
    this.BodyToString(writer);
    if (this.Version.Envelope != EnvelopeVersion.None)
    {
        writer.WriteEndElement();
        writer.WriteEndElement();
    }
}

The this.WriteStartHeaders(writer); code writes the header tag regardless of the number of headers. It is matched by the writer.WriteEndElement() after the for loop. This writer.WriteEndElement() must be matched with the header tag being written, else the Xml document will be invalid.

So there is no way we can override a virtual method to get rid of the headers: WriteStartHeaders calls the virtual method OnWriteStartHeaders but the tag closing prevents simply shutting it off). We have to change the whole ToString() method in order to remove any header-related structure, to arrive at:

- write start of envelope
- write start of body
- write body
- write end of body
- write end of envelope

Solutions

In the above pseudocode, we have control on everything but the "write body" part. All methods called in the initial ToString(XmlDictionaryWriter writer) are public except BodyToString. So we will need to call it through reflection or whichever method fits your needs. Writing a message without its headers simply becomes:

private void ProcessMessage(Message msg, XmlDictionaryWriter writer)
{
    msg.WriteStartEnvelope(writer); // start of envelope
    msg.WriteStartBody(writer); // start of body

    var bodyToStringMethod = msg.GetType()
        .GetMethod("BodyToString", BindingFlags.Instance | BindingFlags.NonPublic);
    bodyToStringMethod.Invoke(msg, new object[] {writer}); // write body

    writer.WriteEndElement(); // write end of body
    writer.WriteEndElement(); // write end of envelope
}

Now we have a way to get our message content without the headers. But how should this method be invoked?

We only want the message without headers as a string

Great, we don't need to care about overriding the ToString() method that then calls the initial writing of the message. Just create a method in your program that takes a Message and an XmlDictionaryWriter and call it to get the message without its headers.

We want the ToString() method to return the message without headers

This one is a bit more complicated. We cannot easily inherit from the Message class because we would need to pull out a lot of dependencies out of the System.ServiceModel assembly. I won't go there in this answer.

What we can do is use the capabilities of some frameworks to create a proxy around an existing object and to intercept some calls to the original object in order to replace/enhance its behavior: I'm used to Castle Dynamic proxy so let's use that.

We want to intercept the ToString() method so we create a proxy around the Message object we are using and add an interceptor to replace the ToString method of the Message with our implementation:

var msg = Message.CreateMessage(MessageVersion.Soap11, "*");
msg.Headers.Clear();

var proxyGenerator = new Castle.DynamicProxy.ProxyGenerator();
var proxiedMessage = proxyGenerator.CreateClassProxyWithTarget(msg, new ProxyGenerationOptions(),
    new ToStringInterceptor());

The ToStringInterceptor needs to do almost the same thing as the initial ToString() method, we will however use our ProcessMessage method defined above:

public class ToStringInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        if (invocation.Method.Name != "ToString")
        {
            invocation.Proceed();
        }
        else
        {
            var result = string.Empty;
            var msg = invocation.InvocationTarget as Message;

            StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
            XmlDictionaryWriter xmlDictionaryWriter =
                XmlDictionaryWriter.CreateDictionaryWriter(new XmlTextWriter(stringWriter));

            try
            {
                ProcessMessage(msg, xmlDictionaryWriter);
                xmlDictionaryWriter.Flush();
                result = stringWriter.ToString();
            }
            catch (XmlException ex)
            {
                result = "ErrorMessage";
            }
            invocation.ReturnValue = result;
        }
    }

    private void ProcessMessage(Message msg, XmlDictionaryWriter writer)
    {
        // same method as above
    }
}

And here we are: calls to the ToString() method of the message will now return a envelope without headers. We can pass the message to other parts of the framework and know it should mostly work: direct calls to some of the internal plumbing of Message can still produce the initial output but short of a full reimplementation we cannot control that.

Points of note

  • This is the shortest way to removing the headers I found. The fact that the header serialisation in the writer was not handled in one virtual function only was a big problem. The code doesn't give you much wriggle room.
  • This implementation doesn't use the same XmlWriter as the one used in the original implementation of ToString() in the Message, EncodingFallbackAwareXmlTextWriter. This class is internal in System.ServiceModel and pulling it out is left as an exercice to the reader. As a result, the output differs slightly since the xml is not formatted with the simple XmlTextWriter I use.
  • The interceptor could simply have parsed the xml returned from the initial ToString() call and removed the headers node before letting the value bubble up. This is another viable solution.

Raw code

public class ToStringInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        if (invocation.Method.Name != "ToString")
        {
            invocation.Proceed();
        }
        else
        {
            var result = string.Empty;
            var msg = invocation.InvocationTarget as Message;

            StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
            XmlDictionaryWriter xmlDictionaryWriter =
                XmlDictionaryWriter.CreateDictionaryWriter(new XmlTextWriter(stringWriter));

            try
            {
                ProcessMessage(msg, xmlDictionaryWriter);
                xmlDictionaryWriter.Flush();
                result = stringWriter.ToString();
            }
            catch (XmlException ex)
            {
                result = "ErrorMessage";
            }
            invocation.ReturnValue = result;
        }
    }

    private void ProcessMessage(Message msg, XmlDictionaryWriter writer)
    {
        msg.WriteStartEnvelope(writer);
        msg.WriteStartBody(writer);

        var bodyToStringMethod = msg.GetType()
            .GetMethod("BodyToString", BindingFlags.Instance | BindingFlags.NonPublic);
        bodyToStringMethod.Invoke(msg, new object[] { writer });

        writer.WriteEndElement();
        writer.WriteEndElement();
    }
}

internal class Program
{
    private static void Main(string[] args)
    {
        var msg = Message.CreateMessage(MessageVersion.Soap11, "*");
        msg.Headers.Clear();

        var proxyGenerator = new Castle.DynamicProxy.ProxyGenerator();
        var proxiedMessage = proxyGenerator.CreateClassProxyWithTarget(msg, new ProxyGenerationOptions(),
            new ToStringInterceptor());

        var initialResult = msg.ToString();
        var proxiedResult = proxiedMessage.ToString();

        Console.WriteLine("Initial result");
        Console.WriteLine(initialResult);
        Console.WriteLine();
        Console.WriteLine("Proxied result");
        Console.WriteLine(proxiedResult);

        Console.ReadLine();
    }
}

Solution 3

I did not have your XmlBodyWriter but you could use data contract serializer or your xml body writer But the trick is to use msg.WriteBody. this will omit the headers

var response = "Hello";            
Message msg = Message.CreateMessage(MessageVersion.Soap11, "*",response, new DataContractSerializer(response.GetType()));
msg.Headers.Clear();
var sb = new StringBuilder();
var xmlWriter = new XmlTextWriter(new StringWriter(sb));
msg.WriteBody(xmlWriter);

Solution 4

Should be something like this:

XmlDocument xml = new XmlDocument();
xml.LoadXml(myXmlString); // suppose that myXmlString contains "<Body>...</Body>"

XmlNodeList xnList = xml.SelectNodes("/Envelope/Body");
foreach (XmlNode xn in xnList)
{
  string binary1 = xn["Binary1"].InnerText;
  string binary2 = xn["Binary2"].InnerText;
  Console.WriteLine("Binary: {0} {1}", binary1 , binary2);
}
Share:
10,668
user1483352
Author by

user1483352

Updated on June 15, 2022

Comments

  • user1483352
    user1483352 almost 2 years

    I try to delete the whole SOAP header from a WCF message, just only want to leave the envelope body. Anybody can give me an idea how can do that?

    Create a WCF message like this:

    **string response = "Hello World!";
    Message msg = Message.CreateMessage(MessageVersion.Soap11, "*", new TextBodyWriter(response));
    msg.Headers.Clear();**
    

    The sending SOAP message will be:

    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
      <s:Header />
      <s:Body>
        <Binary>Hello World!</Binary>
      </s:Body>
    </s:Envelope>
    

    But I don't want to the SOAP header element, which I just only need the envelop body.How to remove the header element from a WCF message?