Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Intercept a raw SOAP message

5.00/5 (4 votes)
22 Aug 2012CPOL5 min read 65.8K   5  
How to intercept a raw SOAP message.

Intercept a raw SOAP message

While working with web services, often there are situations when you want to extract the raw SOAP message coming in to and going out of your web service. This is specially the case when you are exposing a web service and the client application is not written by you but some other company and you want to debug a scenario such as when the incoming message is not getting de-serialized to object instance, namespace issues, and so on. If you are writing your web service in ASP.NET then good news is that it is very much possible to intercept the raw SOAP message. In fact, you can work with these raw SOAP messages and do some additional works such encrypt/decrypt its contents, modify message header details, and so on. All this is possible by creating a SOAP extension which can be plugged into HTTP pipeline of ASP.NET engine.

You will find many articles on internet which tell you how to write a SOAP extension and different possibilities that can be achieved with it. But if you are like majority of developers, you just want to get raw SOAP message and want the bare minimum implementation to achieve the same, and I did not find a simple implementation for this purpose with my Google skills so I decided to write one myself, of course with the help of other articles which I found on internet.

I am essentially after a very basic functionality which logs the raw SOAP message to a configured trace listener, and that is exactly what I am going to show you as part of this post. I am going to write a SOAP extension which will intercept the raw SOAP message and log it. The logging destination is to be chosen by you by adding a trace listener to the application so you don’t have to modify the SOAP extension if you choose a different logging destination and so on. You can choose to log to a file, event log, database table, etc, take your pick.

The incoming SOAP message is relatively easy to intercept and requires far less code whereas intercepting outgoing SOAP message requires relatively more work. To keep the matter simple I am going to create a SOAP Extension Library project and write two separate extensions, one which intercepts just the incoming SOAP message and the second one which intercepts incoming as well as outgoing SOAP message.

Below diagram explains the SOAP message processing extremely well. As part of creating a new SOAP extension we will need to override ProcessMessage method, which is invoked for four different stages of message processing. We will also need to override few more methods which are defined as abstract in the base SoapExtension class such as ChainStream.

SOAP Request Handling

Source: http://msdn.microsoft.com/en-us/magazine/cc164007.aspx

As we can see in above diagram, ChainStream method will be called twice in the lifecycle of SOAP message processing, first before the actual web service method is called and second time after the web method call is finished. This method provides us with an opportunity to chain a new stream instance based on our requirements. Let’s see it in action with two new SOAP extensions.

Intercept Incoming SOAP Message

In case of incoming SOAP message, the stream initialized by .NET Framework is accessible and you can reset the position (i.e., CanSeek property returns true). Hence, we don’t really need to chain another stream instance and can reuse the same. Find below its implementation:

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Services.Protocols;
using System.IO;
using System.Diagnostics;

namespace SOAPExtensionsLib
{
    public class IncomingSOAPMessageInterceptExtension : SoapExtension
    {
        public override Stream ChainStream(Stream stream)
        {
            // ChainStream method is called twice in the lifecycle of the SOAP
            // message processing. BEFORE the actual web service operation is
            // invoked and AFTER it has completed. 
            // 
            // In case you just want to intercept the incoming SOAP message then
            // you can read the stream initialized by .Net framework and hence, 
            // you dont need to chain a new stream. Therefore, pass the same 
            // stream back.
            //------------------------------------------------------------
            return stream;
        }

        public override object GetInitializer(Type serviceType)
        {
            return null;
        }

        public override object GetInitializer(LogicalMethodInfo methodInfo, 
                               SoapExtensionAttribute attribute)
        {
            return null;
        }

        public override void Initialize(object initializer)
        {
            // do nothing...
        }

        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeDeserialize:
                    // Incoming message
                    LogMessageFromStream(message.Stream);
                    break;

                case SoapMessageStage.AfterDeserialize:
                    break;

                case SoapMessageStage.BeforeSerialize:
                    break;

                case SoapMessageStage.AfterSerialize:
                    break;
            }
        }

        private void LogMessageFromStream(Stream stream)
        {
            string soapMessage = string.Empty;

            // Just making sure again that we have got a stream which we 
            // can read from AND after reading reset its position 
            //------------------------------------------------------------
            if (stream.CanRead && stream.CanSeek)
            {
                stream.Position = 0;

                StreamReader rdr = new StreamReader(stream);
                soapMessage = rdr.ReadToEnd();


                // IMPORTANT!! - Set the position back to zero on the original 
                // stream so that HTTP pipeline can now process it
                //------------------------------------------------------------
                stream.Position = 0;
            }

            // You have raw SOAP message, log it the way you want. I am using 
            // Trace class as I have configured a trace listener which will 
            // write it to a database table, read:
            // http://girishjjain.com/blog/post/Advanced-Tracing.aspx
            //------------------------------------------------------------
            Trace.WriteLine(soapMessage);
        }
    }
}

You just need to make couple of changes to your web.config file, to make the extension work

  1. In order to enable the new SOAP extension, you need to add it to soap extensions list using following:
  2. You need to add a trace listener as follows (I am using a custom database trace listener but feel free to use anything else like EventLogTraceListener, TextWriterTraceListener, etc):

That’s it, a simple SOAP extension which logs every incoming SOAP message to a configured trace listener. Voila!

Intercept Incoming & Outgoing SOAP Message

Now if you want to intercept output SOAP message as well then, there is little more work needed to be done in the SOAP extension and hence, I have created a separate extension for the purpose. In case of incoming SOAP message, the stream instance created by ASP.NET engine is read-able so you can access it, read its contents, reset position, etc. whereas, in case of outgoing SOAP message (web service method response), the output stream created by the ASP.NET engine is an instance of SoapExtensionStream class which does not support read operations but write only. Essentially speaking, you cannot read output stream contents but you can write to it. Hence, in this case we need to chain a new memory stream which we can read from and will pass this memory stream instance to HTTP pipeline to process the message and capture web service method response, and once done we will read its contents (SOAP response) and copy its contents to the original stream created by ASP.NET engine (of type SoapExtensionStream). Find below its code:

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Services.Protocols;
using System.Diagnostics;
using System.IO;

namespace SOAPExtensionsLib
{
    public class IncomingOutgoingSOAPMessageInterceptExtension : SoapExtension
    {
        Stream _originalStream;
        Stream _workingStream;

        public override Stream ChainStream(Stream stream)
        {
            // ChainStream method is called twice in the lifecycle of the SOAP
            // message processing. BEFORE the actual web service operation is
            // invoked and AFTER it has completed. 
            //
            // In case of outgoing response, .Net framework initializes a stream
            // as an instance of type SoapExtensionStream which does NOT support
            // reading from but write operations only. Therefore, we will chain
            // a local stream instance which will be passed to for processing by
            // actual web service method and will be read from when web service
            // method finishes processing. 
            //
            // Therefore, we need to copy contents from original stream to
            // working stream instance before 
            // Once we have read outgoing SOAP message, we will write contents 
            // from working stream to the original (SoapExtensionStream) instance, 
            // for HTTP pipeline to return it to caller.


            // Store reference to incoming stream locally
            _originalStream = stream;

            // Create a new working stream to work with
            _workingStream = new MemoryStream();
            return _workingStream;
        }

        public override object GetInitializer(Type serviceType)
        {
            return null;
        }

        public override object GetInitializer(LogicalMethodInfo methodInfo, 
                               SoapExtensionAttribute attribute)
        {
            return null;
        }

        public override void Initialize(object initializer)
        {
            // do nothing...
        }

        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeDeserialize:
                    // Incoming message
                    Copy(_originalStream, _workingStream);
                    LogMessageFromStream(_workingStream);
                    break;

                case SoapMessageStage.AfterDeserialize:
                    break;

                case SoapMessageStage.BeforeSerialize:
                    break;

                case SoapMessageStage.AfterSerialize:
                    // Outgoing message
                    LogMessageFromStream(this._workingStream);
                    Copy(this._workingStream, this._originalStream);
                    break;
            }
        }

        private void LogMessageFromStream(Stream stream)
        {
            string soapMessage = string.Empty;

            // Just making sure again that we have got a stream which we 
            // can read from AND after reading reset its position 
            //------------------------------------------------------------
            if (stream.CanRead && stream.CanSeek)
            {
                stream.Position = 0;

                StreamReader rdr = new StreamReader(stream);
                soapMessage = rdr.ReadToEnd();
                

                // IMPORTANT!! - Set the position back to zero on the original 
                // stream so that HTTP pipeline can now process it
                //------------------------------------------------------------
                stream.Position = 0;
            }

            // You have raw SOAP message, log it the way you want. I am using 
            // Trace class as I have a configured a trace listener which will 
            // write it to database table
            // http://girishjjain.com/blog/post/Advanced-Tracing.aspx
            //------------------------------------------------------------
            Trace.WriteLine(soapMessage);
        }

        private void Copy(Stream from, Stream to)
        {
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.Write(reader.ReadToEnd());
            writer.Flush();
        }
    }
}

Feel free to use the extension depending upon your requirements, all that you would need to do is place the dll in the bin folder and make couple of changes to web.config file and you are done, absolutely no changes required to your application. Simple implementation for intercepting raw SOAP messages, that’s what I was after to begin with.

Vande Mataram!

(A salute to motherland)

P.S. In addition to blogging, I use Twitter to share tips, links, etc. My Twitter handle is: @girishjjain

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)