简体   繁体   English

如何访问 SOAP 响应

[英]How do I get access to SOAP response

(If anything here needs clarification/ more detail please let me know.) (如果这里有什么需要澄清/更多细节,请告诉我。)

I have an application (C#, 2.* framework) that interfaces with a third-party webservice using SOAP.我有一个应用程序(C#、2.* 框架),它使用 SOAP 与第三方网络服务交互。 I used thinktecture's WSCF add-in against a supplied WSDL to create the client-side implementation.我针对提供的 WSDL 使用 thinktecture 的 WSCF 插件来创建客户端实现。 For reasons beyond my control the SOAP message exchange uses WSE2.0 for security (the thinctecture implementation had to be modified to include the WSE2.0 reference).由于我无法控制的原因,SOAP 消息交换使用 WSE2.0 来保证安全性(必须修改 Thinctecture 实现以包含 WSE2.0 参考)。 In addition to the 'normal' data package I attach a stored X509 cert and a binary security token from a previous call to a different web service.除了“普通”数据包之外,我还附加了一个存储的 X509 证书和一个来自先前对不同 Web 服务的调用的二进制安全令牌。 We are using SSL encryption of some sort - I don't know the details.我们正在使用某种 SSL 加密 - 我不知道细节。

All the necessary serialization/deserialization is contained in the web service client - meaning when control is returned to me after calling the client the entire XML string contained in the SOAP response is not available to me - just the deserialized components.所有必要的序列化/反序列化都包含在 Web 服务客户端中——这意味着在调用客户端后将控制权返回给我时,SOAP 响应中包含的整个 XML 字符串对我来说是不可用的——只有反序列化的组件。 Don't get me wrong - I think that's good because it means I don't have to do it myself.不要误会我的意思 - 我认为这很好,因为这意味着我不必自己做。

However, in order for me to have something worth storing/archiving I am having to re-serialize the data at the root element.但是,为了让我拥有值得存储/存档的东西,我必须在根元素处重新序列化数据。 This seems like a waste of resources since my result was in the SOAP response.这似乎是一种资源浪费,因为我的结果是在 SOAP 响应中。

Now for my question: How can I get access to a 'clear' version of the SOAP response so that I don't have to re-serialize everything for storage/archiving?现在我的问题是:如何访问 SOAP 响应的“清晰”版本,以便我不必重新序列化所有内容以进行存储/归档?

Edit- My application is a 'formless' windows app running as a network service - triggered by a WebsphereMQ client trigger monitor.编辑 - 我的应用程序是作为网络服务运行的“无形”Windows 应用程序 - 由 WebsphereMQ 客户端触发器监视器触发。 I don't think ASP.NET solutions will apply.认为ASP.NET 解决方案不会适用。

Edit - Since the consensus so far is that it doesn't matter whether my app is ASP.NET or not then I will give CodeMelt's (and by extension Chris's) solution a shot.编辑 - 由于到目前为止的共识是我的应用程序是否为 ASP.NET 并不重要,那么我将尝试使用 CodeMelt(以及扩展 Chris 的)解决方案。

You can utilize SoapExtension from existing WSE2.0 framework to intercept the responses from the server.您可以利用现有 WSE2.0 框架中的 SoapExtension 来拦截来自服务器的响应。

public class MyClientSOAPExtension : SoapExtension
{

     Stream oldStream;
     Stream newStream;

     // Save the Stream representing the SOAP request or SOAP response into
     // a local memory buffer.
     public override Stream ChainStream( Stream stream )
     {
            oldStream = stream;
            newStream = new MemoryStream();
            return newStream;
     }

    public override void ProcessMessage(SoapMessage message)
    {
       switch (message.Stage)
        {
            case SoapMessageStage.BeforeDeserialize:
                // before the XML deserialized into object.
                break;
            case SoapMessageStage.AfterDeserialize:
                break;        
            case SoapMessageStage.BeforeSerialize:
                break;
            case SoapMessageStage.AfterSerialize:
                break;            
            default:
                throw new Exception("Invalid stage...");
        }       
    }
}

At stage of SoapMessageStage.BeforeDeserialize, You can read the expected data you want from oldstream (eg use XmlReader).在 SoapMessageStage.BeforeDeserialize 阶段,您可以从 oldstream 中读取您想要的预期数据(例如使用 XmlReader)。 Then store the expected data somewhere for yourself to use and also you need forward the old stream data to the newstream for web service later stage to use the data, eg deserialize XML into objects.然后将预期的数据存储在某个地方供自己使用,并且您还需要将旧的流数据转发到新的流以供 Web 服务后期使用数据,例如将 XML 反序列化为对象。

The sample of logging all the traffic for the web service from MSDN 从 MSDN 记录 Web 服务的所有流量的示例

Here is an example you can setup using Visual studio web reference to http://footballpool.dataaccess.eu/data/info.wso?WSDL这是一个示例,您可以使用 Visual Studio Web 参考来设置http://footballpool.dataaccess.eu/data/info.wso?WSDL

Basically, you must insert in the webservice call chain a XmlReader spyer that will reconstruct the raw XML.基本上,您必须在 Web 服务调用链中插入一个 XmlReader 间谍程序,它将重建原始 XML。

I believe this way is somehow simpler that using SoapExtensions.我相信这种方式比使用 SoapExtensions 更简单。

Solution solution was inspired by http://orbinary.com/blog/2010/01/getting-the-raw-soap-xml-sent-via-soaphttpclientprotocol/解决方案的灵感来自http://orbinary.com/blog/2010/01/getting-the-raw-soap-xml-sent-via-soaphttpclientprotocol/

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Reflection;
using System.Xml;


namespace ConsoleApplication1 {

    public class XmlReaderSpy : XmlReader {
        XmlReader _me;
        public XmlReaderSpy(XmlReader parent) {
            _me = parent;
        }

        /// <summary>
        /// Extracted XML.
        /// </summary>
        public string Xml;

        #region Abstract method that must be implemented
        public override XmlNodeType NodeType {
            get {

                return _me.NodeType;
            }
        }

        public override string LocalName {
            get {
                return _me.LocalName;
            }
        }

        public override string NamespaceURI {
            get {
                return _me.NamespaceURI;
            }
        }

        public override string Prefix {
            get {
                return _me.Prefix;
            }
        }

        public override bool HasValue {
            get { return _me.HasValue; }
        }

        public override string Value {
            get { return _me.Value; }
        }

        public override int Depth {
            get { return _me.Depth; }
        }

        public override string BaseURI {
            get { return _me.BaseURI; }
        }

        public override bool IsEmptyElement {
            get { return _me.IsEmptyElement; }
        }

        public override int AttributeCount {
            get { return _me.AttributeCount; }
        }

        public override string GetAttribute(int i) {
            return _me.GetAttribute(i);
        }

        public override string GetAttribute(string name) {
            return _me.GetAttribute(name);
        }

        public override string GetAttribute(string name, string namespaceURI) {
            return _me.GetAttribute(name, namespaceURI);
        }

        public override void MoveToAttribute(int i) {
            _me.MoveToAttribute(i);
        }

        public override bool MoveToAttribute(string name) {
            return _me.MoveToAttribute(name);
        }

        public override bool MoveToAttribute(string name, string ns) {
            return _me.MoveToAttribute(name, ns);
        }

        public override bool MoveToFirstAttribute() {
            return _me.MoveToFirstAttribute();
        }

        public override bool MoveToNextAttribute() {
            return _me.MoveToNextAttribute();
        }

        public override bool MoveToElement() {
            return _me.MoveToElement();
        }

        public override bool ReadAttributeValue() {
            return _me.ReadAttributeValue();
        }

        public override bool Read() {
            bool res = _me.Read();

            Xml += StringView();


            return res;
        }

        public override bool EOF {
            get { return _me.EOF; }
        }

        public override void Close() {
            _me.Close();
        }

        public override ReadState ReadState {
            get { return _me.ReadState; }
        }

        public override XmlNameTable NameTable {
            get { return _me.NameTable; }
        }

        public override string LookupNamespace(string prefix) {
            return _me.LookupNamespace(prefix);
        }

        public override void ResolveEntity() {
            _me.ResolveEntity();
        }

        #endregion


        protected string StringView() {
            string result = "";

            if (_me.NodeType == XmlNodeType.Element) {
                result = "<" + _me.Name;

                if (_me.HasAttributes) {
                    _me.MoveToFirstAttribute();
                    do {
                        result += " " + _me.Name + "=\"" + _me.Value + "\"";
                    } while (_me.MoveToNextAttribute());

                    //Let's put cursor back to Element to avoid messing up reader state.
                    _me.MoveToElement();
                }

                if (_me.IsEmptyElement) {
                    result += "/";
                }

                result += ">";
            }

            if (_me.NodeType == XmlNodeType.EndElement) {
                result = "</" + _me.Name + ">";
            }

            if (_me.NodeType == XmlNodeType.Text || _me.NodeType == XmlNodeType.Whitespace) {
                result = _me.Value;
            }



            if (_me.NodeType == XmlNodeType.XmlDeclaration) {
                result = "<?"  + _me.Name + " " +   _me.Value + "?>";
            }

            return result;

        }
    }

    public class MyInfo : ConsoleApplication1.eu.dataaccess.footballpool.Info {             

        protected XmlReaderSpy _xmlReaderSpy;

        public string Xml {
            get {
                if (_xmlReaderSpy != null) {
                    return _xmlReaderSpy.Xml;
                }
                else {
                    return "";
                }
            }
        }


        protected override XmlReader GetReaderForMessage(System.Web.Services.Protocols.SoapClientMessage message, int bufferSize) {          
            XmlReader rdr = base.GetReaderForMessage(message, bufferSize);
            _xmlReaderSpy = new XmlReaderSpy((XmlReader)rdr);
            return _xmlReaderSpy;
        }

    }

    class Program {
        static void Main(string[] args) {

            MyInfo info = new MyInfo();
            string[] rest = info.Cities();

            System.Console.WriteLine("RAW Soap XML response :\n"+info.Xml);
            System.Console.ReadLine();
        }
    }
}

Old thread, but in case others are looking to do this today: these ideas of leveraging SoapExtension or creating 'spy' classes are great, but don't work in .NET Core.旧线程,但如果其他人今天希望这样做:利用 SoapExtension 或创建“间谍”类的这些想法很棒,但在 .NET Core 中不起作用。

@mting923's suggestion to use IClientMessageInspector approach works in .NET Core 3.1; @mting923 建议使用 IClientMessageInspector 方法适用于 .NET Core 3.1; see here: Get SOAP Message before sending it to the WebService in .NET .请参见此处: 在将 SOAP 消息发送到 .NET 中的 WebService 之前获取它

A generated SOAP proxy class is still just a WCF client under the hood, and so the IClientMessageInspector approach works a treat, even for an .NET Core Azure Function calling an older SOAP web service.生成的 SOAP 代理类仍然只是底层的 WCF 客户端,因此 IClientMessageInspector 方法是一种享受,即使对于调用旧 SOAP Web 服务的 .NET Core Azure 函数也是如此。 The following works for me in a .NET Core 3.1 Azure Function:以下在 .NET Core 3.1 Azure 函数中对我有用:

public class SoapMessageInspector : IClientMessageInspector
{
    public string LastRequestXml { get; private set; }
    public string LastResponseXml { get; private set; }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        LastRequestXml = request.ToString();
        return request;
    }

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        LastResponseXml = reply.ToString();
    }
}

public class SoapInspectorBehavior : IEndpointBehavior
{
    private readonly SoapMessageInspector inspector_ = new SoapMessageInspector();

    public string LastRequestXml => inspector_.LastRequestXml;
    public string LastResponseXml => inspector_.LastResponseXml;

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.ClientMessageInspectors.Add(inspector_);
    }
}

And then it can be set up like this:然后它可以像这样设置:

    var client = new ServiceClient();
    var soapInspector = new SoapInspectorBehavior();
    client.Endpoint.EndpointBehaviors.Add(soapInspector);

After invoking a web service call on the client proxy, soapInspector.LastRequestXml and soapInspector.LastResponseXml will contain the raw SOAP request and response (as strings).在客户端代理上调用 Web 服务之后, soapInspector.LastRequestXmlsoapInspector.LastResponseXml将包含原始 SOAP 请求和响应(作为字符串)。

Inspired by jfburdet, I wanted to see if it was possible to directly intercept at stream/byte level rather than reconstructing XML.受jfburdet的启发,想看看是否可以直接在流/字节级别进行拦截,而不是重构XML。 And it is!它是! See code below:见下面的代码:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Services.Protocols;
using System.Xml;

using Test.MyWebReference;

namespace Test {
    /// <summary>
    /// Adds the ability to retrieve the SOAP request/response.
    /// </summary>
    public class ServiceSpy : OriginalService {
        private StreamSpy writerStreamSpy;
        private XmlTextWriter xmlWriter;

        private StreamSpy readerStreamSpy;
        private XmlTextReader xmlReader;

        public MemoryStream WriterStream {
            get { return writerStreamSpy == null ? null : writerStreamSpy.ClonedStream; }
        }

        public XmlTextWriter XmlWriter {
            get { return xmlWriter; }
        }

        public MemoryStream ReaderStream {
            get { return readerStreamSpy == null ? null : readerStreamSpy.ClonedStream; }
        }

        public XmlTextReader XmlReader {
            get { return xmlReader; }
        }

        protected override void Dispose(bool disposing) {
            base.Dispose(disposing);
            DisposeWriterStreamSpy();
            DisposeReaderStreamSpy();
        }

        protected override XmlWriter GetWriterForMessage(SoapClientMessage message, int bufferSize) {
            // Dispose previous writer stream spy.
            DisposeWriterStreamSpy();

            writerStreamSpy = new StreamSpy(message.Stream);
            // XML should always support UTF8.
            xmlWriter = new XmlTextWriter(writerStreamSpy, Encoding.UTF8);

            return xmlWriter;
        }

        protected override XmlReader GetReaderForMessage(SoapClientMessage message, int bufferSize) {
            // Dispose previous reader stream spy.
            DisposeReaderStreamSpy();

            readerStreamSpy = new StreamSpy(message.Stream);
            xmlReader = new XmlTextReader(readerStreamSpy);

            return xmlReader;
        }

        private void DisposeWriterStreamSpy() {
            if (writerStreamSpy != null) {
                writerStreamSpy.Dispose();
                writerStreamSpy.ClonedStream.Dispose();
                writerStreamSpy = null;
            }
        }

        private void DisposeReaderStreamSpy() {
            if (readerStreamSpy != null) {
                readerStreamSpy.Dispose();
                readerStreamSpy.ClonedStream.Dispose();
                readerStreamSpy = null;
            }
        }

        /// <summary>
        /// Wrapper class to clone read/write bytes.
        /// </summary>
        public class StreamSpy : Stream {
            private Stream wrappedStream;
            private long startPosition;
            private MemoryStream clonedStream = new MemoryStream();

            public StreamSpy(Stream wrappedStream) {
                this.wrappedStream = wrappedStream;
                startPosition = wrappedStream.Position;
            }

            public MemoryStream ClonedStream {
                get { return clonedStream; }
            }

            public override bool CanRead {
                get { return wrappedStream.CanRead; }
            }

            public override bool CanSeek {
                get { return wrappedStream.CanSeek; }
            }

            public override bool CanWrite {
                get { return wrappedStream.CanWrite; }
            }

            public override void Flush() {
                wrappedStream.Flush();
            }

            public override long Length {
                get { return wrappedStream.Length; }
            }

            public override long Position {
                get { return wrappedStream.Position; }
                set { wrappedStream.Position = value; }
            }

            public override int Read(byte[] buffer, int offset, int count) {
                long relativeOffset = wrappedStream.Position - startPosition;
                int result = wrappedStream.Read(buffer, offset, count);
                if (clonedStream.Position != relativeOffset) {
                    clonedStream.Position = relativeOffset;
                }
                clonedStream.Write(buffer, offset, result);
                return result;
            }

            public override long Seek(long offset, SeekOrigin origin) {
                return wrappedStream.Seek(offset, origin);
            }

            public override void SetLength(long value) {
                wrappedStream.SetLength(value);
            }

            public override void Write(byte[] buffer, int offset, int count) {
                long relativeOffset = wrappedStream.Position - startPosition;
                wrappedStream.Write(buffer, offset, count);
                if (clonedStream.Position != relativeOffset) {
                    clonedStream.Position = relativeOffset;
                }
                clonedStream.Write(buffer, offset, count);
            }

            public override void Close() {
                wrappedStream.Close();
                base.Close();
            }

            protected override void Dispose(bool disposing) {
                if (wrappedStream != null) {
                    wrappedStream.Dispose();
                    wrappedStream = null;
                }
                base.Dispose(disposing);
            }
        }
    }
}

The MSDN Library includes example code for obtaining the XML of both the request and the response that you can use to archive it. MSDN 库包含用于获取请求和响应的 XML 的示例代码,您可以使用这些代码将其存档。 Obviously you'll have to make some changes since the example stores data in a text file, but it isn't too complicated.显然,由于示例将数据存储在文本文件中,因此您必须进行一些更改,但这并不太复杂。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM