WCF message:How to remove the SOAP Header element?
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 ofToString()
in theMessage
,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 simpleXmlTextWriter
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);
}
user1483352
Updated on June 15, 2022Comments
-
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?