4

我尝试从 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 消息中删除标头元素?

4

4 回答 4

7

选项 1:使用 bacicHttpBinding,它不会在 header 中添加内容(未配置安全性时)

选项 2:实现自定义消息编码器并在那里剥离标题。在此之前的任何地方,wcf 都有可能将标题添加回来。在此处查看示例编码器

于 2012-06-27T09:59:29.393 回答
4

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

一些背景

该类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);数量如何,代码都会写入标头标签。它与writer.WriteEndElement()for 循环之后的匹配。这writer.WriteEndElement() 必须与正在写入的 header 标签匹配,否则 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()然后调用消息的初始写入的方法。只需在您的程序中创建一个接受 aMessage和 an的方法,XmlDictionaryWriter然后调用它来获取没有标头的消息。

我们希望该ToString()方法返回没有标头的消息

这个有点复杂。我们不能轻易地从Message类继承,因为我们需要从System.ServiceModel 程序集中提取大量依赖项。我不会在这个答案中去那里。

我们可以做的是使用某些框架的功能围绕现有对象创建代理并拦截对原始对象的一些调用以替换/增强其行为:我习惯于Castle Dynamic 代理,所以让我们使用它。

我们想要拦截 ToString() 方法,所以我们围绕Message我们正在使用的对象创建一个代理,并添加一个拦截器来用我们的实现替换该ToString方法Message

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 的一些内部管道仍然可以产生初始输出,但如果没有完全重新实现,我们无法控制它。

注意事项

  • 这是删除我找到的标题的最短方法。编写器中的标头序列化没有仅在一个虚拟函数中处理这一事实是一个大问题。该代码没有给您太多回旋余地。
  • 此实现与XmlWriter, 的原始实现中使用ToString()Message不同EncodingFallbackAwareXmlTextWriter。这个类在 System.ServiceModel 中是内部的,把它拉出来留给读者作为练习。结果,输出略有不同,因为 xml 没有XmlTextWriter使用我使用的简单格式。
  • 拦截器可以简单地解析从初始ToString()调用返回的 xml,并在让值冒泡之前删除 headers 节点。这是另一个可行的解决方案。

原始代码

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();
    }
}
于 2014-10-22T10:27:01.723 回答
2

我没有您的 XmlBodyWriter,但您可以使用数据合同序列化程序或您的 xml 正文编写器,但诀窍是使用 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);
于 2014-10-21T16:41:07.760 回答
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);
}
于 2014-10-22T15:05:49.677 回答