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
.
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:
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)
{
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)
{
}
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeDeserialize:
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;
if (stream.CanRead && stream.CanSeek)
{
stream.Position = 0;
StreamReader rdr = new StreamReader(stream);
soapMessage = rdr.ReadToEnd();
stream.Position = 0;
}
Trace.WriteLine(soapMessage);
}
}
}
You just need to make couple of changes to your web.config file, to make the extension work
- In order to enable the new SOAP extension, you need to add it to soap extensions list using following:
- 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:
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)
{
_originalStream = stream;
_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)
{
}
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeDeserialize:
Copy(_originalStream, _workingStream);
LogMessageFromStream(_workingStream);
break;
case SoapMessageStage.AfterDeserialize:
break;
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
LogMessageFromStream(this._workingStream);
Copy(this._workingStream, this._originalStream);
break;
}
}
private void LogMessageFromStream(Stream stream)
{
string soapMessage = string.Empty;
if (stream.CanRead && stream.CanSeek)
{
stream.Position = 0;
StreamReader rdr = new StreamReader(stream);
soapMessage = rdr.ReadToEnd();
stream.Position = 0;
}
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