Logging an Xml SOAP Request from a C# client before sending it

OK. So I wanted to log the xml SOAP request from a C# client before the client actually sent it. The server is referenced using the Web Reference method with these steps:

  1. Right-click on References and select Add Service Reference…
  2. Click Advanced (bottom left of window)
  3. Click Add Web Reference… (bottom left of window)
  4. Enter the URL and click the arrow.
  5. Enter a namespace and click Add reference.

So this is NOT a WCF client hitting a WCF service, so I can’t use a ClientMessageInspector. However, I needed a similar feature. The first option I found output the Xml to the Visual Studio output window, though the output wasn’t clean. Fortunately, I found a more ClientMessageInspector-like method thanks to this stackoverflow post. It seems there is a SoapExtension object I can inherit from. The example in the stackoverflow post was for a server, but it worked from the client as well.

My steps:

  1. Create the project in Visual Studio.
  2. Add Log4Net from NuGet and add Log4Net settings in the Program.cs file.
  3. Add a WebRefence to the Service.
  4. Call the service in main().
    using log4net;
    using log4net.Appender;
    using log4net.Config;
    using log4net.Layout;
    using System.Reflection;
    using System.Text;
    using YourProject.Extensions;
    
    namespace YourProject
    {
        class Program
        {
            private static ILog Logger
            {
                get { return _Logger ?? (_Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType)); }
            } private static ILog _Logger;
    
            static void Main(string[] args)
            {
                ConfigureLog4Net();
                CallWebService();
            }
    
            private static void CallWebService()
            {
               // call service here
            }
    
            private static void ConfigureLog4Net()
            {
                var appender = new FileAppender()
                {
                    Layout = new SimpleLayout(),
                    File = Assembly.GetExecutingAssembly().Location + ".log",
                    Encoding = Encoding.UTF8,
                    AppendToFile = true,
                    LockingModel = new FileAppender.MinimalLock()
                };
                appender.ActivateOptions();
                BasicConfigurator.Configure(appender);
            }
        }
    }
    
  5. Add my Xml helper class so we can log the SOAP Xml with Pretty Xml. See this post: An Xml class to linearize xml, make pretty xml, and encoding in UTF-8 or UTF-16.
  6. Add the SoapLoggerExtension : SoapExtension class.
    using System;
    using System.IO;
    using System.Reflection;
    using System.Web.Services.Protocols;
    using log4net;
    
    namespace YourProject.Extensions
    {
        public class SoapLoggerExtension : SoapExtension
        {
            private static ILog Logger
            {
                get { return _Logger ?? (_Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType)); }
            } private static ILog _Logger;
    
            private Stream _OldStream;
            private Stream _NewStream;
    
            public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
            {
                return null;
            }
    
            public override object GetInitializer(Type serviceType)
            {
                return null;
            }
    
            public override void Initialize(object initializer)
            {
    
            }
    
            public override Stream ChainStream(Stream stream)
            {
                _OldStream = stream;
                _NewStream = new MemoryStream();
                return _NewStream;
            }
    
            public override void ProcessMessage(SoapMessage message)
            {
                switch (message.Stage)
                {
                    case SoapMessageStage.BeforeSerialize:
                        break;
                    case SoapMessageStage.AfterSerialize:
                        Log(message, "AfterSerialize");
                        CopyStream(_NewStream, _OldStream);
                        _NewStream.Position = 0;
                        break;
                    case SoapMessageStage.BeforeDeserialize:
                        CopyStream(_OldStream, _NewStream);
                        Log(message, "BeforeDeserialize");
                        break;
                    case SoapMessageStage.AfterDeserialize:
                        break;
                }
            }
    
            public void Log(SoapMessage message, string stage)
            {
                _NewStream.Position = 0;
                Logger.Debug(stage);
                var reader = new StreamReader(_NewStream);
                string requestXml = reader.ReadToEnd();
                _NewStream.Position = 0;
                if (!string.IsNullOrWhiteSpace(requestXml))
                    Logger.Debug(new Xml(requestXml).PrettyXml);
            }
    
            public void ReverseIncomingStream()
            {
                ReverseStream(_NewStream);
            }
    
            public void ReverseOutgoingStream()
            {
                ReverseStream(_NewStream);
            }
    
            public void ReverseStream(Stream stream)
            {
                TextReader tr = new StreamReader(stream);
                string str = tr.ReadToEnd();
                char[] data = str.ToCharArray();
                Array.Reverse(data);
                string strReversed = new string(data);
    
                TextWriter tw = new StreamWriter(stream);
                stream.Position = 0;
                tw.Write(strReversed);
                tw.Flush();
            }
    
            private void CopyStream(Stream fromStream, Stream toStream)
            {
                try
                {
                    StreamReader sr = new StreamReader(fromStream);
                    StreamWriter sw = new StreamWriter(toStream);
                    sw.WriteLine(sr.ReadToEnd());
                    sw.Flush();
                }
                catch (Exception ex)
                {
                    string message = String.Format("CopyStream failed because: {0}", ex.Message);
                    Logger.Error(message, ex);
                }
            }
        }
    }
    
  7. Add a the Soap to the App.config.
    <configuration>
    
      <!-- Other stuff here -->
    
      <system.web>
        <webServices>
          <soapExtensionTypes>
            <add type="YourProject.Extensions.SoapLoggerExtension, YourProjectAssembly" priority="2" group="Low" />
          </soapExtensionTypes>
        </webServices>
      </system.web>
    </configuration>
    
  8. Now make your web service call and the SOAP xml will be logged to a file.

Happy day.

Leave a Reply

How to post code in comments?