Pull to refresh

Получение текста запросов из SoapHttpClientProtocol

Reading time 4 min
Views 4.9K
У .NET есть несколько вариантов создания SOAP клиента, одним из них является его генерация с помощью wsdl.exe. На выходе получаем файл (поскольку пишу я на C#, то генерировал cs, соответственно), основой которого является класс, унаследованный от SoapHttpClientProtocol. Подробнее тут.

С моей точки зрения, это достаточно удобный способ, к тому же сам клиент можно подразогнать с помощью sgen.exe (очень хороший пример). Тем не менее есть у него один очень серьезный недостаток — это отсутствие штатной возможности получения текста запроса/ответа. А это было бы крайне удобно при первичной отладке сервисов, разборе ошибок и, самое главное, при возможных разбирательствах со стороны, эти самые сервисы предоставляющей.

Впрочем, если очень хочется, то нужно сделать.

Основная идея


Вполне хорошим выходом будет каким-либо образом переопределить SoapHttpClientProtocol и добавить эту самую возможность логирования. Вариант для получения запросов клиента, представленный тут, был взят за основу, после чего к нему была «прикручена» возможность логирования и ответов сервера.

Переопределям SoapHttpClientProtocol


Релизуем свой класс, пусть будет SoapHttpClientProtocolSpy, унаследованный от SoapHttpClientProtocol соответственно. Для перехвата запросов клиента переопределяем метод GetWriterForMessage, а для перехвата ответов сервера — GetReaderForMessage. Первый возвращает XmlWriter, второй — XmlReader; вместо них и вернем собственные реализации, которые будут позволять получить XMl, через них проходящий.

Получаем следующий класс:

SoapHttpClientProtocolSpy
   public class SoapHttpClientProtocolSpy: SoapHttpClientProtocol
    {
        private XmlWriterSpy writer;
        private XmlReaderSpy reader;
        public SoapHttpClientProtocolSpy() : base(){}

        protected override XmlWriter GetWriterForMessage(SoapClientMessage message, int bufferSize)
        {
            writer = new XmlWriterSpy(base.GetWriterForMessage(message, bufferSize));
            return writer;
        }

        protected override XmlReader GetReaderForMessage(SoapClientMessage message, int bufferSize)
        {
            reader = new XmlReaderSpy(base.GetReaderForMessage(message, bufferSize));
            return reader;
        }

        public string XmlRequest => reader?.Xml;
        public string XmlResponce => writer?.Xml;
    }


XmlWriterSpy и XmlReaderSpy — это декораторы для XmlWriter и XmlReader.

XmlWriterSpy


В сущности, сама реализация заключается в добавлении StringWriter, в который и будем складировать текст запроса по ходу обработки. То есть необходимо переопределить все методы записи так, чтобы они вызывали штатный обработчик и передавали результат в StringWriter.

Реализация
    public class XmlWriterSpy : XmlWriter
    {
       //это же декоратор, поэтому просто пользуемся той реализацией, которая придет
        private XmlWriter _me;
        private XmlTextWriter _bu;
        private StringWriter _sw;

        public XmlWriterSpy(XmlWriter implementation)
        {
            _me = implementation;
            _sw = new StringWriter();
            _bu = new XmlTextWriter(_sw);
            _bu.Formatting = Formatting.Indented;
        }

        public override void Flush()
        {
            _me.Flush();
            _bu.Flush();
            _sw.Flush();
        }
         public string Xml => _sw?.ToString();

        public override void Close() { _me.Close(); _bu.Close(); }
        public override string LookupPrefix(string ns) { return _me.LookupPrefix(ns); }
        public override void WriteBase64(byte[] buffer, int index, int count) { _me.WriteBase64(buffer, index, count); _bu.WriteBase64(buffer, index, count); }
        public override void WriteCData(string text) { _me.WriteCData(text); _bu.WriteCData(text); }
   //И так далее, в том же духе

    }


Еще раз спасибо данной статье за это.

XmlWriterSpy


Здесь идея абсолютно та же. Но только с реализацией придется немного повозиться. С одной стороны, достаточно влезть только в 1 метод(Read), с другой стороны, там чтение из потока, и не так просто его не сломать. Идею взял отсюда.

Реализация
    public class XmlReaderSpy : XmlReader
    {
       //это же декоратор, поэтому просто пользуемся той реализацией, которая придет
        private XmlReader _baseXmlReader;
        StringWriter _sw;
        public string Xml => _sw?.ToString();
        public XmlReaderSpy(XmlReader xmlReader)
        {
            _sw = new StringWriter();
            _baseXmlReader = xmlReader;
        }
     

        public override bool Read()
        {
//получаем прочитанную ноду
                var res = _baseXmlReader.Read();
//каждый тип ноды придется обрабатывать немного по-разному
                switch (_baseXmlReader.NodeType)
                {
                    case XmlNodeType.Element:
                        _sw.Write("<" + _baseXmlReader.Name);
                           while (_baseXmlReader.MoveToNextAttribute())
                                _sw.Write(" " + _baseXmlReader.Name + "='" + _baseXmlReader.Value + "'");
                            _sw.Write(_baseXmlReader.HasValue || _baseXmlReader.IsEmptyElement ? "/>" : ">");
                       //поскольку мы перемещались по элементу, надо вернуться на исходную
                        _baseXmlReader.MoveToElement();
                        break;
                 
                    case XmlNodeType.Text:
                        _sw.Write(_baseXmlReader.Value);
                        break;
                    case XmlNodeType.CDATA:
                        _sw.Write(_baseXmlReader.Value);
                        break;
                    case XmlNodeType.ProcessingInstruction:
                        _sw.Write("<?" + _baseXmlReader.Name + " " + _baseXmlReader.Value + "?>");
                        break;
                    case XmlNodeType.Comment:
                        _sw.Write("<!--" + _baseXmlReader.Value + "-->");
                        break;
                    case XmlNodeType.Document:
                        _sw.Write("<?xml version='1.0'?>");
                        break;
                    case XmlNodeType.Whitespace:
                        _sw.Write(_baseXmlReader.Value);
                        break;
                    case XmlNodeType.SignificantWhitespace:
                        _sw.Write(_baseXmlReader.Value);
                        break;
                    case XmlNodeType.EndElement:
                        _sw.Write("</" + _baseXmlReader.Name + ">");
                        break;
                }
                return res;
        }
    }


Все остальные методы переопределяем на простой вызов того же метода у _baseXmlReader.

Использование


Вспомним о сгенерированном классе, теперь нужно всего лишь унаследовать его от SoapHttpClientProtocolSpy. Стоит сказать, что такая реализация работает, даже если сервер вернет ошибку (то есть она будет тоже залогирована). Ну а далее вызов метода выглядит примерно так:


using (var worker = new Service())
                {
                    try
                    {
                        res = worker.Metod(....);
                        Log.Info((worker?.XmlRequest ?? "")+(worker?.XmlResponce ?? ""));
                    }
                    catch (System.Exception ex)
                    {
                        Log.Error((worker?.XmlRequest ?? "")+(worker?.XmlResponce ?? ""));
                        throw ex;
                    }
                }

PS. С моей точки зрения, достаточно удобным будет вынести это все в отдельную либу. Единственное, что будет постоянно напрягать, так это необходимость после обновления клиента лезть в файл и заменять там SoapHttpClientProtocol, но это мелочи.
Tags:
Hubs:
+10
Comments 11
Comments Comments 11

Articles