Click here to Skip to main content
11,925,073 members (55,057 online)
Click here to Skip to main content
Add your own
alternative version


34 bookmarked

Efficient Tracing Using SOAP Extensions in .NET

, 11 Feb 2015 CPOL
Rate this:
Please Sign up or sign in to vote.
Describes how to avoid loss of performance when implementing SOAP extensions.


Some time ago, I had to add tracing of outgoing SOAP messages to our Web-Service. Tracing had to be applied only for certain web-calls, depending on the configuration. When I read about SOAP extensions and suggested using them to my colleague, he was absolutely against the idea. After deep investigation, I saw why he was. SOAP extensions, if used as described in MSDN and some other resources, really affect your application performance.

Problems with the Common Implementation

  • You need to perform copying of the original stream to an in-memory stream in order to read from it multiple times.
  • It happens for both incoming and outgoing messages.
  • At the time you chain the stream, you don't have any information about what method of what class is currently called and can't access properties of the proxy object.
  • You can't choose which extensions must be used at run-time, they are selected declaratively.

There are several ways of initializing SOAP extensions:

  • Class constructor - The class constructor is called every time a SOAP extension is instantiated, and is typically used to initialize member variables.
  • GetInitializer - GetInitializer however is called just once, the first time a SOAP request to an XML Web Service's method is made. It has two overloaded versions. If a custom attribute is applied to the XML Web Service method, the first GetInitializer method is invoked. This allows the SOAP extension to interrogate the LogicalMethodInfo of an XML Web Service method for prototype information, or to access extension-specific data passed by a class deriving from SoapExtensionAttribute. Unfortunately, it's not my case. The second version is called when a SOAP extension is added in the web.config. It has only one parameter - the Web Service type.
  • Initialize - Initialize is called every time a SOAP request is made to an XML Web Service method, but has an advantage over the class constructor, in that the object initialized in GetInitializer is passed to it.

None of these features were helpful to me, as I had to control the execution process depending on the run-time data.

The most terrible thing with SOAP extensions is that the only method, where you can replace the net thread with the in-memory one - ChainStream - doesn't have any parameter except the thread, and is called before the more reasonable method ProcessMessage. You don't even know if it is a server or client message. ProcessMessage receives all the necessary information to make a decision, but when it is called, it's too late to change the stream. And, once you have replaced the stream in the ChainStream method, you always have to copy it to the real stream, which affects performance and requires more memory.

How to Implement SOAP Extensions Efficiently

After some days of investigation of this problem, I managed to persuade my colleague into using SOAP extensions with some improvements in the implementation, which involve using of a special switchable stream.

Special Stream

This stream is inherited from the abstract Stream class, and just delegates all the standard method calls to one of the two internal streams. The first is the original "old" stream, the second is a MemoryStream that is instantiated only on demand.

Shown below is the implementation of this class:

#region TraceExtensionStream

/// <summary>
/// Special switchable stream
/// </summary>
internal class TraceExtensionStream : Stream
    #region Fields

    private Stream innerStream;
    private readonly Stream originalStream;


    #region .ctor

    /// <summary>
    /// Constructs an instance of the stream
    /// wrapping the original stream into it
    /// </summary>
    internal TraceExtensionStream(Stream originalStream)
        innerStream = this.originalStream = originalStream;


    #region New public members

    /// <summary>
    /// Creates a new memory stream and makes it active
    /// </summary>
    public void SwitchToNewStream()
        innerStream = new MemoryStream();

    /// <summary>
    /// Copies data from the old stream to the new in-memory stream
    /// </summary>
    public void CopyOldToNew()
        //innerStream = new MemoryStream((int)originalStream.Length);
        Copy(originalStream, innerStream);
        innerStream.Position = 0;

    /// <summary>
    /// Copies data from the new stream to the old stream
    /// </summary>
    public void CopyNewToOld()
        Copy(innerStream, originalStream);

    /// <summary>
    /// Returns true if the active inner stream is a new stream,
    /// i.e. SwitchToNewStream has been called
    public bool IsNewStream
            return (innerStream != originalStream);

    /// <summary>
    /// A link to the active inner stream
    /// </summary>
    public Stream InnerStream
        get { return innerStream; }


    #region Private members

    private static void Copy(Stream from, Stream to)
        const int size = 4096;
        byte[] bytes = new byte[4096];
        int numBytes;
        while((numBytes = from.Read(bytes, 0, size)) > 0)
            to.Write(bytes, 0, numBytes);


    #region Overridden members

    public override IAsyncResult BeginRead(byte[] buffer, int offset, 
           int count, AsyncCallback callback, object state)
        return innerStream.BeginRead(buffer, offset, count, callback, state);

    public override IAsyncResult BeginWrite(byte[] buffer, int offset, 
           int count, AsyncCallback callback, object state)
        return innerStream.BeginWrite(buffer, offset, count, callback, state);

    //other overriden abstract members of Stream go down here



SOAP Extension

To create a SOAP extension, you have to implement some abstract methods, such as ChainStream, GetIntializer, Initialize, and ProcessMessage. ChainStream will look a little simpler than in the MSDN example. It just wraps a stream in TraceExtensionStream:

/// <summary>
/// Replaces soap stream with our smart stream
/// </summary>
public override Stream ChainStream(Stream stream)
    traceStream = new TraceExtensionStream(stream);
    return traceStream;

traceStream here is a field, where we store a reference to our stream for future use.

We have nothing to do with the following methods, so we just live them blank:

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

public override object GetInitializer(Type WebServiceType)
    return null;

public override void Initialize(object initializer)

Information is passed to the method ProcessMessage in a parameter of type SoapMessage. Actually, an instance of either ClientSoapMessage or ServerSoapMessage is passed, and we can easily check the parameter type. Here, you can separate the client messages from the server messages. As we decided before, in this example, we are interested only in the client messages.

The class ClientSoapMessage has another interesting property - Client. It is a link to the client proxy class derived from SoapHttpClientProtocol. (ServerSoapMessage in turn has a property called Server). If we manage to extend it, we can pass any information to the Web-Service extension at run-time!

Let the clients that will support tracing implement the interface ITraceable, declared like this:

/// <summary>
/// Interface that a proxy class should implement to support tracing
/// </summary>
public interface ITraceable
    bool IsTraceRequestEnabled { get; set; }
    bool IsTraceResponseEnabled { get; set; }
    string ComponentName { get; set; }

It has the following members:

  • IsTraceRequestEnabled - returns true, if dumping of SOAP requests is on.
  • IsTraceResponseEnabled - returns true, if dumping of SOAP responses is on.
  • ComponentName - a name of the component from which the call is performed to mark traced messages with.

Now, we declare a private method in the extension class that tries to get the ITraceable instance from the parameter of ProcessMessage:

/// <summary>
/// Tries to get ITraceable instance
/// </summary>
private ITraceable GetTraceable(SoapMessage message)
    SoapClientMessage clientMessage = message as SoapClientMessage;
    if (clientMessage != null)
        return clientMessage.Client as ITraceable;

    return null;

Now, let's implement the ProcessMessage itself.

It is called four times for a single web call, each at a certain stage. The stage can be read from the Stage property of the SoapMessage, and it may have one of the four values:

  • BeforeSerialize - occurs before the client request (or server response) is serialized. Here, we can prepare our smart stream for buffering, if needed.
  • AfterSerialize - occurs after the client request (or server response) is serialized. Now, we can write the buffer to the log.
  • BeforeDeserialize - occurs before the client response (or server request) is deserialized. Here, we can copy the response to the buffer and save it to the log. After that, we must make the buffer active and reset its position.
  • AfterDeserialize - occurs after the client response (or server request) is deserialized. We won't do anything at this stage.

Here is the implementation:

public override void ProcessMessage(SoapMessage message)
    ITraceable traceable = GetTraceable(message);
    //If proxy is not configured to be traced, return
    if (traceable == null) return;
    switch (message.Stage)
        case SoapMessageStage.BeforeSerialize:
            //If tracing is enabled, switch to memory buffer
            if (traceable.IsTraceRequestEnabled)
        case SoapMessageStage.AfterSerialize:
            //If message was written to memory buffer, 
            //write its content to log and copy to the SOAP stream
            if (traceStream.IsNewStream)
                traceStream.Position = 0;
                WriteToLog(DumpType.Request, traceable);
                traceStream.Position = 0;
        case SoapMessageStage.BeforeDeserialize:
            //If tracing is enabled, copy SOAP stream 
            //to the new stream and write its content to log
            if (traceable.IsTraceResponseEnabled)
                WriteToLog(DumpType.Response, traceable);
                traceStream.Position = 0;

That's it. Now, you only need to make your client protocol to support ITraceable.

Extending SoapHttpClientProtocol

If you implemented your client proxy class (SoapHttpClientProtocol) manually, it is not a problem to add an additional interface to support. But, if it was generated automatically, you probably don't want to modify the auto-generated file. Lickily, in that file, it's declared as partial. It means that the proxy class can be extended in another file.

public partial class MyService : ITraceable
    private string componentName;
    private bool isTraceRequestEnabled;
    private bool isTraceResponseEnabled;

    public bool IsTraceRequestEnabled
        get { return isTraceRequestEnabled; }
        set { isTraceRequestEnabled = value; }

    public bool IsTraceResponseEnabled
        get { return isTraceResponseEnabled; }
        set { isTraceResponseEnabled = value; }

    public string ComponentName
        get { return componentName; }
        set { componentName = value; }

Now, you only need to set the values of these properties, and you can control the use of your SOAP extension without permanently affecting performance.

How to Use the Code

Obvously, you need an application with a reference to a web-service. It can be a web application, Windows application, or another web-service.

First, copy the enclosed code file into your project. You may want to change the namespace and the tracing method.

Then you should extend your web-service client proxy class as described above. If there is a logic defining whether logging should occur or not, it will go there.

Finally, make sure to include a reference to your SOAP extension in the web.config (or app.config) file. You should get something similar to this.

        <add type="Ideafixxxer.SoapDumper.TraceExtension" priority="1" group="0"/>


I hope this article helps somebody. Any comments and questions are welcome.


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


About the Author

Software Developer (Senior) EPAM Systems
Canada Canada
No Biography provided

You may also be interested in...

Comments and Discussions

QuestionMake relation between a pair of request & response Pin
yogiCsharp5-Jun-15 23:50
memberyogiCsharp5-Jun-15 23:50 
AnswerRe: Make relation between a pair of request & response Pin
ideafixxxer30-Sep-15 12:39
professionalideafixxxer30-Sep-15 12:39 
QuestionDoesn't call SOAP Extension Pin
Zharkyn Kassenov17-Mar-15 11:08
memberZharkyn Kassenov17-Mar-15 11:08 
AnswerRe: Doesn't call SOAP Extension Pin
ideafixxxer17-Mar-15 11:25
professionalideafixxxer17-Mar-15 11:25 
AnswerRe: Doesn't call SOAP Extension Pin
breakpoint8-May-15 11:36
memberbreakpoint8-May-15 11:36 
QuestionClient Side Pin
Member 1095585210-Feb-15 3:57
memberMember 1095585210-Feb-15 3:57 
AnswerRe: Client Side Pin
ideafixxxer10-Feb-15 11:11
professionalideafixxxer10-Feb-15 11:11 
NewsRe: Client Side Pin
ideafixxxer10-Feb-15 11:42
professionalideafixxxer10-Feb-15 11:42 
QuestionHow to implement this?? Pin
Tridip Bhattacharjee19-Nov-12 4:08
memberTridip Bhattacharjee19-Nov-12 4:08 
AnswerRe: How to implement this?? Pin
ideafixxxer19-Nov-12 8:52
memberideafixxxer19-Nov-12 8:52 
GeneralRe: How to implement this?? Pin
Tridip Bhattacharjee19-Nov-12 20:52
memberTridip Bhattacharjee19-Nov-12 20:52 
GeneralRe: How to implement this?? Pin
ctvn200224-Jul-14 9:28
memberctvn200224-Jul-14 9:28 
GeneralRe: How to implement this?? Pin
Member 1080717515-Aug-14 23:08
memberMember 1080717515-Aug-14 23:08 
GeneralRe: How to implement this?? Pin
dufagundes29-Oct-14 9:13
memberdufagundes29-Oct-14 9:13 
GeneralRe: How to implement this?? Pin
stambo18-Nov-14 3:05
memberstambo18-Nov-14 3:05 
GeneralI downloaded code that you have provided but no logfile is created Pin
GatewayNeha113-Jun-11 1:40
memberGatewayNeha113-Jun-11 1:40 
AnswerRe: I downloaded code that you have provided but no logfile is created Pin
borisco18-Jun-11 4:07
memberborisco18-Jun-11 4:07 
QuestionNot Working Pin
Member 76961701-Mar-11 7:58
memberMember 76961701-Mar-11 7:58 
GeneralDisposing of a memory stream [modified] Pin
kkgb12-Apr-10 2:38
memberkkgb12-Apr-10 2:38 
AnswerRe: Disposing of a memory stream Pin
ideafixxxer12-Apr-10 4:49
memberideafixxxer12-Apr-10 4:49 
GeneralAdd SoapExtensionattribute Pin
MathewUthup3-Oct-09 18:42
memberMathewUthup3-Oct-09 18:42 
GeneralRe: Add SoapExtensionattribute Pin
ideafixxxer4-Oct-09 7:29
memberideafixxxer4-Oct-09 7:29 
GeneralDumpType Pin
pavlo200927-Apr-09 2:33
memberpavlo200927-Apr-09 2:33 
AnswerRe: DumpType Pin
ideafixxxer27-Apr-09 3:51
memberideafixxxer27-Apr-09 3:51 
QuestionI do not understand the difference between the old and the new way of implementation Pin
Sivajish25-Mar-09 6:53
memberSivajish25-Mar-09 6:53 
AnswerRe: I do not understand the difference between the old and the new way of implementation Pin
ideafixxxer25-Mar-09 7:29
memberideafixxxer25-Mar-09 7:29 
GeneralRe: I do not understand the difference between the old and the new way of implementation Pin
Sivajish25-Mar-09 10:50
memberSivajish25-Mar-09 10:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.151125.3 | Last Updated 11 Feb 2015
Article Copyright 2009 by ideafixxxer
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid