Как получить доступ к ответу SOAP

(Если что-то здесь нужно уточнить / более подробно, пожалуйста, дайте мне знать.)

У меня есть приложение (C#, 2.* framework), которое взаимодействует со сторонним веб-сервисом, используя SOAP. Я использовал WSCF-надстройку thinktecture против поставляемого WSDL для создания реализации на стороне клиента. По независящим от меня причинам обмен сообщениями SOAP использует WSE2.0 для безопасности (реализация thinctecture должна была быть изменена, чтобы включить ссылку на WSE2.0). В дополнение к "обычному" пакету данных я прикрепляю сохраненный сертификат X509 и двоичный токен безопасности от предыдущего вызова к другому веб-сервису. Мы используем какое-то SSL-шифрование - я не знаю деталей.

Вся необходимая сериализация / десериализация содержится в клиенте веб-службы - то есть, когда мне возвращают управление после вызова клиента, вся строка XML, содержащаяся в ответе SOAP, не доступна для меня - только десериализованные компоненты. Не поймите меня неправильно - я думаю, что это хорошо, потому что это означает, что мне не нужно делать это самому.

Однако для того, чтобы у меня было что-то, что стоит хранить / архивировать, мне нужно повторно сериализовать данные в корневом элементе. Это кажется пустой тратой ресурсов, так как мой результат был в ответе SOAP.

Теперь мой вопрос: как я могу получить доступ к "чистой" версии ответа SOAP, чтобы мне не пришлось повторно сериализовать все для хранения / архивирования?

Редактировать - Мое приложение - это "бесформенное" приложение для Windows, работающее в качестве сетевой службы, которое запускается монитором триггера клиента WebsphereMQ. Я не думаю, что решения ASP.NET будут применяться.

Редактировать - поскольку до сих пор единодушно мнение, что не имеет значения, является ли мое приложение ASP.NET или нет, тогда я попробую решение CodeMelt (и, соответственно, Криса).

4 ответа

Решение

Вы можете использовать SoapExtension из существующей среды WSE2.0 для перехвата ответов от сервера.

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, вы можете прочитать ожидаемые данные, которые вы хотите из старого потока (например, использовать XmlReader). Затем сохраните ожидаемые данные где-нибудь для себя, чтобы использовать их, а также вам необходимо переслать старые потоковые данные в новый поток для последующего этапа веб-службы, чтобы использовать эти данные, например, десериализовать XML в объекты.

Пример регистрации всего трафика для веб-службы из MSDN

Вот пример, который вы можете настроить, используя веб-ссылку Visual Studio на http://footballpool.dataaccess.eu/data/info.wso?WSDL

По сути, вы должны вставить в цепочку вызовов веб-сервиса spyer XmlReader, который восстановит необработанный 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();
        }
    }
}

Старый поток, но на тот случай, если другие захотят сделать это сегодня: эти идеи использования SoapExtension или создания "шпионских" классов прекрасны, но не работают в.NET Core.

Предложение @mting923 использовать подход IClientMessageInspector работает в.NET Core 3.1; см. здесь: Получите сообщение SOAP перед его отправкой в ​​WebService в.NET.

Сгенерированный прокси-класс SOAP по-прежнему является просто клиентом WCF под капотом, поэтому подход IClientMessageInspector работает неплохо, даже для функции.NET Core Azure, вызывающей старую веб-службу SOAP. В функции Azure.NET Core 3.1 для меня работает следующее:

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

После вызова веб-службы на прокси-сервере клиента, soapInspector.LastRequestXml а также soapInspector.LastResponseXml будет содержать необработанный запрос и ответ SOAP (в виде строк).

Вдохновленный 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);
            }
        }
    }
}

Библиотека MSDN содержит пример кода для получения XML-запроса и ответа, который можно использовать для его архивирования. Очевидно, вам придется внести некоторые изменения, поскольку в примере данные хранятся в текстовом файле, но это не слишком сложно.

Другие вопросы по тегам