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:
- Right-click on References and select Add Service Reference…
- Click Advanced (bottom left of window)
- Click Add Web Reference… (bottom left of window)
- Enter the URL and click the arrow.
- 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:
- Create the project in Visual Studio.
- Add Log4Net from NuGet and add Log4Net settings in the Program.cs file.
- Add a WebRefence to the Service.
- 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); } } }
- 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.
- 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); } } } }
- 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>
- Now make your web service call and the SOAP xml will be logged to a file.
Happy day.