Implementing a custom SOAP extension

In this article I will explain how to implement a custom SOAP extension to be able log all incoming and outgoing SOAP messages passing through a .NET web service.

Sometimes the IIS log file is not enough when debugging or looking for errors in a web service. Let’s say that the web service works like a charm in the development environment, but then when a third party user starts invoking methods in the production environment it stops doing what it was built for. Then you have a need for this little extension, letting you log and analyze all incoming messages as well as the outgoing.

What is this?

A SOAP extension is a piece of code existing between the network wire and the actual web method, and is used both for incoming and outgoing messages. Therefore you will be able to hog a SOAP message and do whatever you like with it. I will explain how to use it for logging purposes.

So if the web service looks like it is processing messages, the IIS log shows a bunch of POSTs, but nothing seems to work.. what’s wrong? The actual SOAP messages aren’t available for deeper analysis, so the need to log these are immense.

How to accomplish this

As most of the computer science stuff around, this is nothing new… The code is available on MSDN, but the background is somewhat missing. There is also one big issue with the extension! It will not give any tracing or logging when invoking the web service methods from a web browser!!! This is because of the fact that the web browsers do not perform an “ordinary POST”, which is strange. I have tried a number of browsers. I had to build a small client to make the web service log the incoming messages.

First of all the extension can be used in two different ways. I will explain the “regular” way, meaning that all web methods will use the extension and the activation and deactivation is controlled from web.config:

<webServices>
<soapExtensionTypes>
<add type="FJSoapTrace.InputOutputTrace, FJSoapTrace" priority="1" group="0"/>
</soapExtensionTypes>
</webServices>

The code will contain support for making individual web methods log the messages to file, controlled by an attribute on the methods:

[FJSoapTrace.InputOutputTrace(Filename="LOG_FILE", Priority=1)]
public string HelloWorld()
{
   return "Hello world";
}

In the first code snippet above, the extension is addressed from the webServices tag in web.config. I have decided to implement the extension in a separate assembly and the syntax for activating it is:

<add type="<NAMESPACE.CLASSNAME>, <ASSMEBLY>" priority="1" group="0"/>

The assembly containing the extension class has to be added as a reference to the web service project.

As you can see the filename parameter in the second example is not available in the first. I have decided to put that parameter inside the appSettings section like this:

<appSettings>
<add key="SoapExtensionOutputFile" value="LOG_FILE"/>
</appSettings>

The code

The code for the extension class looks like this (comments below the code):

namespace FJSoapTrace
{
    public class InputOutputTrace : SoapExtension
    {
        Stream oldStream;
        Stream newStream;
        string filename;

        // Save the Stream representing the SOAP request or SOAP response into
        // a local memory buffer.
        public override Stream ChainStream(Stream stream)
        {
            oldStream = stream;
            newStream = new MemoryStream();
            return newStream;
        }

        // When the SOAP extension is accessed for the first time, the XML Web
        // service method it is applied to is accessed to store the file
        // name passed in, using the corresponding SoapExtensionAttribute.
        public override object GetInitializer(
                                                LogicalMethodInfo methodInfo,
                                                SoapExtensionAttribute attribute)
        {
            return ((InputOutputTraceAttribute)attribute).Filename;
        }

        // The SOAP extension was configured to run using a configuration file
        // instead of an attribute applied to a specific XML Web service
        // method.
        public override object GetInitializer(Type WebServiceType)
        {
            // Return a file name to log the trace information to, based on the
            // type.
            string outFile = ConfigurationManager.AppSettings["SoapExtensionOutputFile"];
            if (outFile != null)
                return outFile;
            else
                return WebServiceType.FullName + ".log";
        }

        // Receive the file name stored by GetInitializer and store it in a
        // member variable for this specific instance.
        public override void Initialize(object initializer)
        {
            filename = (string)initializer;
        }

        //  If the SoapMessageStage is such that the SoapRequest or
        //  SoapResponse is still in the SOAP format to be sent or received,
        //  save it out to a file.
        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    break;
                case SoapMessageStage.AfterSerialize:
                    WriteOutput(message);
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    WriteInput(message);
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
            }
        }

        public void WriteOutput(SoapMessage message)
        {
            newStream.Position = 0;
            FileStream fs = new FileStream(filename, FileMode.Append,
                FileAccess.Write);
            StreamWriter w = new StreamWriter(fs);

            string soapString = (message is SoapServerMessage) ?
                                                "SoapResponse" :
                                                "SoapRequest";
            w.WriteLine("<<<<<" + soapString + " at " + DateTime.Now);
            w.Flush();
            Copy(newStream, fs);
            w.Close();
            newStream.Position = 0;
            Copy(newStream, oldStream);
        }

        public void WriteInput(SoapMessage message)
        {
            Copy(oldStream, newStream);
            FileStream fs = new FileStream(filename, FileMode.Append,
                FileAccess.Write);
            StreamWriter w = new StreamWriter(fs);
            w.WriteLine();
            string soapString = (message is SoapServerMessage) ?
                "SoapRequest" : "SoapResponse";
            w.WriteLine(">>>>>" + soapString +
                " at " + DateTime.Now);
            w.Flush();
            newStream.Position = 0;
            Copy(newStream, fs);
            w.Close();
            newStream.Position = 0;
        }

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

    // Create a SoapExtensionAttribute for the SOAP Extension that can be
    // applied to an XML Web service method.
    [AttributeUsage(AttributeTargets.Method)]
    public class InputOutputTraceAttribute : SoapExtensionAttribute
    {

        private string filename = "log.txt";
        private int priority;

        public override Type ExtensionType
        {
            get { return typeof(InputOutputTrace); }
        }

        public override int Priority
        {
            get { return priority; }
            set { priority = value; }
        }

        public string Filename
        {
            get
            {
                return filename;
            }
            set
            {
                filename = value;
            }
        }
    }
}

Comments:

  • On lines 35-37 the appSettings key is checked and used if set.
  • GetInitializer on line 21 is used when activating the extension via method attributes.
  • ProcessMessage on line 52 is the method hogging the actual SOAP, BeforeDeSerialize and AfterSerialize.
  • WriteOutput (line 69) and WriteInput (line 87) append the SOAP messages to the log file.

Result

The output from the extension may look like this:

>>>>>SoapRequest at 2008-10-23 15:58:40
<?xml version="1.0" encoding="utf-8"?><soap:Envelope... </soap:Body></soap:Envelope>
<<<<<SoapResponse at 2008-10-23 15:58:42
<?xml version="1.0" encoding="utf-8"?><soap:Envelope... </soap:Body></soap:Envelope>

Now you are able to look at the SOAP messages coming into your web service. In my case the consumer of my service saw nothing wrong, the IIS log showed a bunch of POSTs, but the service wasn’t doing what it was supposed to do. I implemented the extension and then I was able to log the messages in the production environment.

Note

Don’t forget to write a client when trying out the extension! It will not be used when invoking the web methods from a browser.

Read more

Tagged with: , ,
Posted in .NET, ASP.NET, C#, Tips & tricks, Uncategorized, Visual Studio
19 comments on “Implementing a custom SOAP extension
  1. Peter Karlsson says:

    It’s always a pleasure to read your articles.

  2. rohit says:

    hello,
    i tried following code ,but it’s not giving any solution.
    where you use file name there we use full path of the file name.

    plz reply soon

    Thanks

  3. Fredde says:

    @rohit

    Thank you for the question!

    The parameter I have identified as LOG_FILE supports full path and the extension will log to the file as long as the web application has enough user rights for creation and modification of the file.

    I have made a couple of changes to the extension class, but I haven’t yet published them here.

    Please let me know if you have now luck specifying the full path to the log file!

  4. rohit says:

    Hello fredde,

    Thanks For Your Reply..

    The Web Application i am working on has full rights for creation and modification of the file.
    I am still not getting any solution.May be your new publication would help me.

    Thanks & Regards
    Rohit

  5. Fredde says:

    Hello again Rohit!

    Maybe I missunderstand you?
    Are you trying to set the LOG_FILE to a full path, e.g. something like “c:\logs\somelog.txt” in the appSettings section (key=”SoapExtensionOutputFile”) in web.config? This should be OK, but I will look into this as soon as possible.
    Please let me know if I have missunderstood the problem!

    Maybe you can send me an e-mail on blog-comment(at)freddes.se with some code and a more detailed description of the problem?

    Thanks for your input!
    /Fredde

  6. rohit says:

    i sent to you some code of my application so plz tell me where i am wrong as soon as possible

    Thanks

  7. Erinc Arikan says:

    Great article, I already succeeded doing what you’ve done. But I am wondering how to implement an extension to log web service call methods, number of bytes being returned, along with original parameters of the web method call.

    Any ideas?

  8. Do you mind if I post an extract from your article on my site, I will put a link back to your blog?

  9. Fredde says:

    @Colon Cleanse LUKE: What is it you are writing about?

  10. colonics says:

    I have read every word you have printed here. It gives me a good outlook in a lot of subjects now. I’m sure it can do the same for other readers as well.

  11. bloggsida says:

    Bra inlägg, tack. sökte i Google, intressant läsning

  12. Me sirvió mucho leer este artículo y las opiniones de los demás. Muchísimas gracias por la opinión

  13. LorencoCiz says:

    Did you downloaded Wikileaks docs? Give me link plz
    Thanks

  14. AssangeLivekaw says:

    Is it possible to contact administration?
    Thanks
    bye bye ;) )

  15. That is very fascinating, You’re an excessively professional blogger. I have joined your feed and stay up for looking for extra of your excellent post. Additionally, I have shared your site in my social networks!- Elegant London Escorts, 65-67 Brewer Street, Floor: 2, London W1F 9UP. Phone: 020 3011 2941

  16. Shravan says:

    super!

  17. Zara says:

    Hi ,
    I really need your help. I have similar issue with rohit .
    I am not sure what’s wrong.
    I tired many things,,, and I really need this.
    Could you please help me?

    Zara

  18. Fantastic web site. Lots of helpful info here. I’m sending it to some pals ans also sharing in delicious. And naturally, thank you for your sweat!

  19. Angelo says:

    Hello, Look at this blog, we provide another example of how to do that.

    http://www.systemdeveloper.info/2013/11/trace-soap-requestresponse-xml-with.html

    I hope you find it helpful

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>