【问题标题】:WCF message:How to remove the SOAP Header element?WCF 消息:如何删除 SOAP 标头元素?
【发布时间】:2012-06-26 16:34:05
【问题描述】:

我尝试从 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 发送它。
  • 为什么要去掉header?
  • 你好 Mangist,WCF 消息是抽象类,它不能实例化。唯一的创建方法是调用 CreateMessage 函数
  • 嗨,John。包含 SOAP 标头的 SOAP 消息更有意义。但为了兼容其他一些第三个项目。第三个系统是一个旧系统。
  • 我遇到了同样的问题,发现 SOAP 标头是通过启用 WCF 跟踪并在调试器下运行添加的。

标签: c# .net wcf soap wcf-binding


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    一些上下文

    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()必须与正在写入的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() 方法,然后调用消息的初始写入。只需在您的程序中创建一个采用MessageXmlDictionaryWriter 的方法,然后调用它来获取没有标头的消息。

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

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

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

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

    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()MessageEncodingFallbackAwareXmlTextWriter 中的原始实现中使用的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();
        }
    }
    

    【讨论】:

      【解决方案3】:

      我没有您的 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);
      

      【讨论】:

        【解决方案4】:

        应该是这样的:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-27
          相关资源
          最近更新 更多