WCF消息:如何删除SOAP头元素?

5

我想删除WCF消息中的整个SOAP头,只想留下信封主体。有人能给我一个想法怎么做吗?

按照以下方式创建WCF消息:

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

发送的SOAP消息将是:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header />
  <s:Body>
    <Binary>Hello World!</Binary>
  </s:Body>
</s:Envelope>

我不想要SOAP头元素,只需要信封主体。如何从WCF消息中删除头元素?


你可不可以自己创建 SOAP 消息,而不使用 Message.CreateMessage(),而是使用 StringBuilder 或 XmlSerializer?这样你就可以按照自己的需求构建字符串,并使用 WebClient 发送它。 - Jon
你为什么想要去掉头部? - John Saunders
嗨Mangist,WCF消息是抽象类,无法实例化。创建的唯一方法是调用CreateMessage函数。 - user1483352
嗨John,一个包含SOAP头的SOAP消息更有意义。但为了兼容其他第三方项目,这个第三方系统是一个旧系统。 - user1483352
我曾经遇到过同样的问题,后来发现是由于启用了WCF跟踪并在调试器下运行导致添加了SOAP头。 - Mitja Gustin
4个回答

7

选项1:使用basicHttpBinding,它不会向标头添加内容(当未配置安全性时)

选项2:实现自定义消息编码器并在其中删除标头。在此之前的任何地方,都有可能wcf会再次添加标头。请参见这里的示例编码器


5

那个问题很棘手:让我们一步一步来

一些背景信息

Message类在其ToString()方法中编写其标头。然后,ToString()调用内部重载ToString(XmlDictionaryWriter writer),然后开始编写:

// 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();
    }
}

这段代码 this.WriteStartHeaders(writer); 会写入头标签,无论有多少个头标签。在 for 循环后,writer.WriteEndElement() 与之匹配。这个 writer.WriteEndElement() 必须与被写入的头标签匹配,否则 XML 文档将无效。
因此,我们无法通过重写虚拟方法来摆脱头部信息:WriteStartHeaders 调用虚拟方法 OnWriteStartHeaders,但标记关闭防止了简单地关闭它。我们必须改变整个 ToString() 方法以删除任何与头相关的结构,以得到:
- write start of envelope
- write start of body
- write body
- write end of body
- write end of envelope

解决方案

在上述伪代码中,我们对一切都有控制,除了“写正文”部分。在最初的ToString(XmlDictionaryWriter writer)中调用的所有方法都是公共的,除了BodyToString。因此,我们需要通过反射或适合您需求的任何方法来调用它。不带头信息地编写消息变得非常简单:

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
}

现在我们有一种方法可以获得没有标题的消息内容。但是,这个方法应该如何调用?
我们只想要字符串形式的没有标题的消息。
太好了,我们不需要关心覆盖 ToString() 方法然后调用消息初始编写的问题。只需在程序中创建一个方法,接受 Message 和 XmlDictionaryWriter,并调用它以获取没有标题的消息。
我们希望 ToString() 方法返回没有标题的消息。
这个比较复杂。我们不能轻易地继承 Message 类,因为我们需要从 System.ServiceModel 程序集中提取大量依赖项。在这个答案中,我不会深入讨论这个问题。
我们可以使用一些框架的能力来创建一个围绕现有对象的代理,并拦截一些对原始对象的调用,以替换/增强其行为:我习惯于使用 Castle Dynamic proxy,所以让我们使用它。
我们希望拦截 ToString() 方法,因此我们在使用的 Message 对象周围创建一个代理,并添加拦截器来替换 Message 的 ToString 方法为我们的实现:
var msg = Message.CreateMessage(MessageVersion.Soap11, "*");
msg.Headers.Clear();

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

"

ToStringInterceptor 需要做的事情与初始的 ToString() 方法几乎相同,但我们将使用上述定义的 ProcessMessage 方法:

"
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
    }
}

在这里,我们调用消息的ToString()方法将返回一个没有头部的信封。我们可以将消息传递给框架的其他部分,并知道它应该大部分工作:对于消息的某些内部管道的直接调用仍然可以产生初始输出,但是除非完全重新实现,否则我们无法控制。
注意事项:
- 这是我找到的去除标题的最短方法。写入器中的标题序列化不仅仅是一个虚拟函数的问题。代码没有给你太多的余地。 - 此实现不使用与Message中ToString()的原始实现中使用的EncodingFallbackAwareXmlTextWriter相同的XmlWriter。这个类在System.ServiceModel中是内部的,将其拉出来留给读者作为练习。因此,由于xml未使用我使用的简单的XmlTextWriter进行格式化,输出略有不同。 - 拦截器可以简单地解析从初始ToString()调用返回的xml,并在让值冒泡之前删除标题节点。这是另一种可行的解决方案。
原始代码:
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();
    }
}

2

我没有你的XmlBodyWriter,但你可以使用数据合同序列化器或你自己的xml body writer。但是诀窍在于使用msg.WriteBody。这将省略头部信息。

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);

2
应该像这样:
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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接