13

(如果这里有什么需要澄清/更多细节,请告诉我。)

我有一个使用 SOAP 与第三方 Web 服务交互的应用程序(C#,2.* 框架)。我针对提供的 WSDL 使用 thinktecture 的 WSCF 插件来创建客户端实现。由于我无法控制的原因,SOAP 消息交换使用 WSE2.0 来保证安全性(必须修改 Thinctecture 实现以包含 WSE2.0 参考)。除了“普通”数据包之外,我还附加了一个存储的 X509 证书和一个来自先前调用不同 Web 服务的二进制安全令牌。我们正在使用某种 SSL 加密——我不知道细节。

所有必要的序列化/反序列化都包含在 Web 服务客户端中——这意味着在调用客户端后将控制权返回给我时,我无法使用 SOAP 响应中包含的整个 XML 字符串——只有反序列化的组件。不要误会我的意思——我认为这很好,因为这意味着我不必自己做。

但是,为了让我有一些值得存储/存档的东西,我必须在根元素处重新序列化数据。这似乎是一种资源浪费,因为我的结果是在 SOAP 响应中。

现在我的问题是:我如何才能访问 SOAP 响应的“清晰”版本,这样我就不必重新序列化所有内容以进行存储/归档?

编辑 - 我的应用程序是作为网络服务运行的“无格式”Windows 应用程序 - 由 WebsphereMQ 客户端触发器监视器触发。我认为ASP.NET 解决方案不会适用。

编辑 - 由于到目前为止的共识是我的应用程序是否是 ASP.NET 并不重要,所以我会给 CodeMelt(以及扩展 Chris 的)解决方案一个机会。

4

5 回答 5

9

您可以利用现有 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...");
        }       
    }
}

在 SoapMessageStage.BeforeDeserialize 阶段,您可以从 oldstream 中读取您想要的预期数据(例如使用 XmlReader)。然后将预期的数据存储在某个地方供自己使用,并且您需要将旧的流数据转发到新的流以供 Web 服务稍后使用数据,例如将 XML 反序列化为对象。

记录来自 MSDN 的 Web 服务的所有流量的示例

于 2008-11-01T23:49:17.623 回答
6

这是一个示例,您可以使用 Visual Studio Web 引用对http://footballpool.dataaccess.eu/data/info.wso?WSDL进行设置

基本上,您必须在 web 服务调用链中插入一个 XmlReader spyer,它将重建原始 XML。

我相信这种方式比使用 SoapExtensions 更简单。

解决方案的灵感来自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();
        }
    }
}
于 2011-03-03T12:54:11.297 回答
4

受 jfburdet 的启发,我想看看是否可以在流/字节级别直接拦截而不是重建 XML。它是!请参见下面的代码:

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);
            }
        }
    }
}
于 2011-12-07T11:21:50.750 回答
4

旧线程,但如果其他人希望今天这样做:利用 SoapExtension 或创建“间谍”类的这些想法很棒,但在 .NET Core 中不起作用。

@mting923 建议使用 IClientMessageInspector 方法适用于 .NET Core 3.1;请参阅此处:在将 SOAP 消息发送到 .NET 中的 WebService 之前获取 SOAP 消息

生成的 SOAP 代理类在底层仍然只是一个 WCF 客户端,因此 IClientMessageInspector 方法很有效,即使对于调用旧 SOAP Web 服务的 .NET Core Azure 函数也是如此。以下适用于 .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_);
    }
}

然后可以这样设置:

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

在客户端代理上调用 Web 服务调用后,soapInspector.LastRequestXmlsoapInspector.LastResponseXml包含原始 SOAP 请求和响应(作为字符串)。

于 2020-08-20T02:28:22.400 回答
0

MSDN 库包含用于获取请求和响应的 XML 的示例代码,您可以使用它来归档它。显然,由于示例将数据存储在文本文件中,因此您必须进行一些更改,但这并不太复杂。

于 2008-11-01T23:26:01.633 回答