How to capture SOAP envelopes when consuming a web service in ASP.NET.

I spend quite a bit of time on the PayPal Developer Community forum trying to help other developers with integration problems.  When asking questions about the PayPal SOAP API, many people will post the SOAP envelope that they're sending to the API.  Since that is the actual request stream that's sent to the web service, it seems like the best way to verify exactly what you're requesting.

ASP.NET has done a great job of abstracting the whole SOAP request/response process.  When you add a web reference to the PayPal SOAP API web service, it appears as a collection of classes with properties and methods for working with the API.  There is no dealing with XML, just instantiate the objects, set the necessary properties, and call the desired methods -- object oriented design at its finest.

In fact, it is abstracted so well, there is no simple way to view the actual XML documents that are sent back and forth between your application and the web service.  After a bit of research, I found that Microsoft has provided the functionality I was looking for in the way of SOAP Extensions.  I found what I needed here:

http://msdn2.microsoft.com/en-us/library/system.web.services.protocols.soapextension(VS.80).aspx

and here:

http://msdn2.microsoft.com/en-us/library/b5e8e7kk(VS.80).aspx.

The basic idea is to create a class that inherits from System.Web.Services.Protocols.SoapExtension.  Register this class in the web.config, (see the second link above) and all SOAP traffic will be routed through your class to be captured, viewed, and modified as desired.  The example shown in MSDN (see the first link above) logs all request and response envelopes to a file.  I modified that example to store the envelopes in static XML documents that I can access when making PayPal API calls.

Here is my modified version of the TraceExtension class from the example code:

using System;
using System.IO;
using System.Web.Services.Protocols;
using System.Xml;

namespace Encore.PayPal.Soap
{
    /// <summary>
    /// SOAP Extension that traces the SOAP request and
    /// SOAP response for the PayPal SOAP API Web service. 
    /// </summary>
    public class TraceExtension : SoapExtension
    {
        private Stream oldStream;
        private Stream newStream;

        private static XmlDocument xmlRequest;
        /// <summary>
        /// Gets the outgoing XML request sent to PayPal
        /// </summary>
        public static XmlDocument XmlRequest
        {
            get { return xmlRequest; }
        }

        private static XmlDocument xmlResponse;
        /// <summary>
        /// Gets the incoming XML response sent from PayPal
        /// </summary>
        public static XmlDocument XmlResponse
        {
            get { return xmlResponse; }
        }

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

        /// <summary>
        /// If the SoapMessageStage is such that the SoapRequest or
        /// SoapResponse is still in the SOAP format to be sent or received,
        /// save it to the xmlRequest or xmlResponse property.
        /// </summary>
        /// <param name="message"></param>
        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    break;
                case SoapMessageStage.AfterSerialize:
                    xmlRequest = GetSoapEnvelope(newStream);
                    CopyStream(newStream, oldStream);
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    CopyStream(oldStream, newStream);
                    xmlResponse = GetSoapEnvelope(newStream);
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
            }
        }

        /// <summary>
        /// Returns the XML representation of the Soap Envelope in the supplied stream.
        /// Resets the position of stream to zero.
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        private XmlDocument GetSoapEnvelope(Stream stream)
        {
            XmlDocument xml = new XmlDocument();
            stream.Position = 0;
            StreamReader reader = new StreamReader(stream);
            xml.LoadXml(reader.ReadToEnd());
            stream.Position = 0;
            return xml;
        }
       
        /// <summary>
        /// Copies a stream.
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        private void CopyStream(Stream from, Stream to)
        {
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
        }
#region NoOp
        /// <summary>
        /// Included only because it must be implemented.
        /// </summary>
        /// <param name="methodInfo"></param>
        /// <param name="attribute"></param>
        /// <returns></returns>
        public override object GetInitializer(LogicalMethodInfo methodInfo,
            SoapExtensionAttribute attribute)
        {
            return null;
        }

        /// <summary>
        /// Included only because it must be implemented.
        /// </summary>
        /// <param name="WebServiceType"></param>
        /// <returns></returns>
        public override object GetInitializer(Type WebServiceType)
        {
            return null;
        }

        /// <summary>
        /// Included only because it must be implemented.
        /// </summary>
        /// <param name="initializer"></param>
        public override void Initialize(object initializer)
        {
        }
#endregion NoOp
    }
}

Here is how to register the class in the web.config:

  <system.web>
    <webServices>
      <soapExtensionTypes>
        <add type="Encore.PayPal.Soap.TraceExtension, Encore.PayPal.Soap" priority="1" group="0" />
      </soapExtensionTypes>
    </webServices>
  </system.web>

To view the SOAP envelopes, just access the TraceExtension.XmlRequest and TraceExtension.XmlResponse XmlDocument properties right after posting the request. 

Print | posted on Sunday, April 06, 2008 1:40 PM

Comments on this post

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
Ah, I've been wondering if such a scheme existed for some time now. Appreciate the writeup!
Left by Brandon Haynes on Jun 11, 2008 9:03 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
can u tell me how thsi class used in our web services or in our project.
i am not understanding where we will use this?
please help me on this regard.
please mail me some good advice on my id rohitnegi09@gmail.com

Thanks
Left by rohit singh negi on Dec 07, 2008 4:03 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
Instead of the Encore.PayPal.Soap namespace, I recommend you create your own namespace and compile this as a class library. Then, you just add a reference to the compiled .dll to your project.

Alternatively, you could just remove the namespace and put the whole class within your project.

Either way, you need to register it in the web.config as above. Then, after making a call to a SOAP web service, just evaluate the TraceExtension.XmlRequest and TraceExtension.XmlResponse properties. They're static properties, so you don't need to create a TraceExtension object.

Regards,
Scott
Left by Scott on Dec 07, 2008 8:27 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
bravo to you, very useful
Left by Dev on Jan 05, 2009 2:11 PM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
Thank You!!!
Left by Nick on Mar 30, 2009 11:44 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
Thank you very much.

For dummies like me who's never created a class library before, in VS 2005:

1. File > New > Project
2. Visual C# > Class Library
3. Enter chosen Namespace for Project Name > OK
4. Rename default class file to chosen Class Name
5. Paste code above into Class, replace with chosen Namespace and Class Name
6. Right click Project > Add Reference > .Net Tab > System.Web & System.Web.Services
7. Right click Project > Build
Left by Jodda on May 22, 2009 3:00 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
Thank you so much
Left by Matt on May 27, 2009 8:22 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
We have been seeing web services being consumed by .NET client. Following code shows how the same can be consumed with classic ASP client on Windows 2000 or higher. Please, ensure the contents of SOAP envelop are same as shown in WSDL file of Web Services. You may see XML result with following code. Make sure to replace comments such as "- Type the Method name ---" with web services method, "Type the Web Services name ----" with name of web services that the client is accessing and "Type ASMX file name without file extension" with asmx file name without extension.
Left by online casino on Jul 13, 2009 10:24 PM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
Great!
Left by Scotty on Aug 04, 2009 10:23 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
For those of you that are implementing this in a non-web application, the TraceExtension class can be registered in the app.config file of your application.
Left by Kenneth on Aug 10, 2009 8:38 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
I'm having trouble. I'm trying to substitute a static xml file and point my web service to the static resource.
An existing connection was forcibly closed by the remote host
I'm fairly certain the xml file is properly formed, I used your code to get the verbatim response, then saved the static xml file; theoretically, it should work.
Any ideas how I can "fake" a soap response with an static xml file?
Left by Tim W on Sep 04, 2009 1:14 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
I'm trying to see why my first call to the PayPal API is failing - I'm grabbing SOAPTrace.TraceExtension.XmlRequest right after my call to SetExpressCheckout (which is sending back an ACK of "Failure", but XMLRequest is empty. Obviously I'm missing something here - anyone have an example that I can view to see where I'm going wrong? Any help would be greatly appreciated!
Left by Karen on Sep 18, 2009 5:15 PM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
Any code example I could show would look exactly like the code here. I do have a working version that you're welcome to play with. You can find it at
http://paypal-soap.encoresystems.net/

-Scott
Left by Scott on Sep 18, 2009 7:41 PM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
Thank you, thank you - clear and concise, unlike so many other solutions.
Left by ikitClaw on Sep 21, 2009 8:05 AM

# re: How to capture SOAP envelopes when consuming a web service in ASP.NET.

Requesting Gravatar...
As soon as I register the class in web.config all of my SoapClient objects throw an is not defined error. I have the TraceExtension.vb class in App_Code. Any ideas?
Left by john on Oct 29, 2009 11:03 AM
Comments have been closed on this topic.