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.