📜 ⬆️ ⬇️

Getting the text of requests from SoapHttpClientProtocol

.NET has several options for creating a SOAP client, one of which is to generate it using wsdl.exe. The output is a file (since I write in C #, I generated cs, respectively), which is based on a class inherited from SoapHttpClientProtocol. Read more here .

From my point of view, this is quite a convenient way, besides, the client itself can be tweaked using sgen.exe (a very good example ). Nevertheless, he has one very serious drawback - the lack of a regular opportunity to receive the text of the request / response. And this would be extremely convenient for the initial debugging of services, the analysis of errors and, most importantly, for possible proceedings from the side, these same services provide.

However, if you really want, you need to do.
')

main idea


It is quite a good way to override the SoapHttpClientProtocol in some way and add this same logging capability. The option for receiving client requests presented here was taken as a basis, after which the possibility of logging and server responses was “screwed” onto it.

Override SoapHttpClientProtocol


We release our class, let it be SoapHttpClientProtocolSpy, inherited from SoapHttpClientProtocol, respectively. To intercept client requests, override the GetWriterForMessage method, and to intercept server responses, GetReaderForMessage. The first returns an XmlWriter, the second returns an XmlReader; instead, we’ll return our own implementations that will allow us to get XMl passing through them.

We get the following class:

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 and XmlReaderSpy are decorators for XmlWriter and XmlReader.

XmlWriterSpy


In essence, the implementation itself consists of adding a StringWriter, into which we will store the query text during processing. That is, it is necessary to override all recording methods so that they call the regular handler and pass the result to the StringWriter.

Implementation
  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); } //  ,     } 


Thanks again to this article for that.

XmlWriterSpy


Here the idea is absolutely the same. But only with the implementation will have to tinker a bit. On the one hand, it is enough to climb only 1 method (Read), on the other hand, there is reading from the stream, and it is not so easy not to break it. The idea took from here .

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


All other methods are overridden by a simple call to the same method for _baseXmlReader.

Using


Recall the generated class, now you just need to inherit it from SoapHttpClientProtocolSpy. It is worth saying that this implementation works even if the server returns an error (that is, it will also be logged). Well, then the method call looks like this:

 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. From my point of view, it will be quite convenient to take it all to a separate lib. The only thing that will constantly strain is the need to climb into the file after updating the client and replace SoapHttpClientProtocol there, but these are trifles.

Source: https://habr.com/ru/post/337672/


All Articles