25

I'm calling an external HTTPS webservice.

In order to check what is wrong, the owner needs the SOAP request I'm sending.

I have a web reference and the generated proxy class generated by VS 2008...

Is there a way to see the SOAP message just before sending it?

I'm thinking in some .net code... because the Sniffers I tried didn't "see" the webservice invocation don't know why.

Kev
  • 118,037
  • 53
  • 300
  • 385
Romias
  • 13,783
  • 7
  • 56
  • 85

6 Answers6

13

You can simply serialize the request object, before subtmit, like this:

var sreq = new SomeSoapRequest();

// ... fill in here ...

var serxml = new System.Xml.Serialization.XmlSerializer(sreq.GetType());
var ms = new MemoryStream();
serxml.Serialize(ms, sreq);
string xml = Encoding.UTF8.GetString(ms.ToArray());

// in xml string you have SOAP request
dizzy128
  • 289
  • 3
  • 11
12

@mting923's suggestion was really helpful, thanks!

Other ideas (e.g. leveraging SoapExtension, or creating 'spy' classes a la How do I get access to SOAP response) are interesting, but can't be used in .NET Core. But a generated SOAP proxy class is still just a WCF client under the hood, and so the IClientMessageInspector approach works a treat, even for an .NET Core Azure Function calling an older SOAP web service.

In the example above, the response wireup was implied but not fully shown. Also, in the latest .NET Core APIs, MessageInspectors is now ClientMessageInspectors and Behaviors is now EndpointBehaviors. So for completeness, the following works for me in a .NET Core 3.1 Azure Function:

public class SoapMessageInspector : IClientMessageInspector
{
    public string LastRequestXml { get; private set; }
    public string LastResponseXml { get; private set; }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        LastRequestXml = request.ToString();
        return request;
    }

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        LastResponseXml = reply.ToString();
    }
}

public class SoapInspectorBehavior : IEndpointBehavior
{
    private readonly SoapMessageInspector inspector_ = new SoapMessageInspector();

    public string LastRequestXml => inspector_.LastRequestXml;
    public string LastResponseXml => inspector_.LastResponseXml;

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.ClientMessageInspectors.Add(inspector_);
    }
}

And then it can be set up like this:

    var client = new ServiceClient();
    var soapInspector = new SoapInspectorBehavior();
    client.Endpoint.EndpointBehaviors.Add(soapInspector);

After invoking a web service call on the client proxy, soapInspector.LastRequestXml and soapInspector.LastResponseXml will contain the raw SOAP request and response (as strings).

Andy Gray
  • 183
  • 2
  • 7
10

What you need is a SoapExtension. There's quite a few good examples here:

How do I get access to SOAP response

Getting RAW Soap Data from a Web Reference Client running in ASP.net

XML Parse error while processing the SOAP response

One of the articles linked to: http://msdn.microsoft.com/en-us/magazine/cc164007.aspx

Also search SO for: https://stackoverflow.com/search?q=SoapExtension

Community
  • 1
  • 1
Kev
  • 118,037
  • 53
  • 300
  • 385
  • The second link has multiple options. The accepted answer is not necessarily the best (all three solutions are good ones). FYI, I liked the Fiddler option the most. – Nullius Jul 08 '13 at 08:39
10

You can use IClientMEssageInspector and IEndpointBehavior to fullfill this. I found using this way can capture the exact soap request likely as the fiddler one:

Create a class like this in the same project:

public class ClientMessageInspector : System.ServiceModel.Dispatcher.IClientMessageInspector
    {
        #region IClientMessageInspector Members
        public string LastRequestXml { get; private set; }

        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {

        }

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            string requestHeaderName = request.Headers.Action.Replace("urn:#",string.Empty);
            LastRequestXml = request.ToString();
            string serializedRequestFile = string.Format(requestHeaderName + "_request_{0}.xml", DateTime.Now.ToString("yyyyMMddHHmmss"));
            string exportedFolder = ConfigurationManager.AppSettings["SubmittedRequestXmLocation"];
            printSoapRequest(request, exportedFolder, serializedRequestFile);

            return request;
        }

        public void printSoapRequest(System.ServiceModel.Channels.Message request, string exportedFolder, string fileName)
        {
            if (exportedFolder.Equals(string.Empty))
                return;

            if (!Directory.Exists(exportedFolder))
            {
                Directory.CreateDirectory(exportedFolder);
            }
            string exportedFile = string.Format("{0}\\{1}", exportedFolder, fileName);
            if (File.Exists(exportedFile))
            {
                File.Delete(exportedFile);
            }

            string strRequestXML = request.ToString();
            XDocument xDoc = XDocument.Parse(strRequestXML);
            XmlWriter xw = XmlWriter.Create(exportedFile);
            xDoc.Save(xw);
            xw.Flush();
            xw.Close();
            LogOutput("Request file exported: " + exportedFile);

        }

    }

    public class CustomInspectorBehavior : IEndpointBehavior
    {
        private readonly ClientMessageInspector clientMessageInspector = new ClientMessageInspector();

        public string LastRequestXml
        {
            get { return clientMessageInspector.LastRequestXml; }
        }

        public string LastResponseXml
        {
            get { return clientMessageInspector.LastRequestXml; }
        }

        public void AddBindingParameters(
            ServiceEndpoint endpoint,
            System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(clientMessageInspector);
        }
    }

Then you can call it like the following:

ProxyClass _class = new ProxyClass();
var requestInterceptor = new CustomInspectorBehavior();
           _client.Endpoint.Behaviors.Add(requestInterceptor);

When you call the service method, it will automatically execute the interceptor and print the output. Using this way you can also manipulate the soap message before sending to the server!

mting923
  • 431
  • 1
  • 6
  • 15
  • I use ***Web Service Reference***, ***not WCF Reference*** – Kiquenet Oct 31 '17 at 13:16
  • If you use Web Service Reference, you can exactly build the whole soap envelope from scratch. However, I don't recommend it due to its solid implementation and modern. – mting923 Oct 31 '17 at 21:31
  • Soap extensions (the accepted answer) are somewhat out of date. In general, this approach - [Message Inspectors](https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/message-inspectors) - are usually the "better" choice. – paulsm4 Mar 25 '18 at 17:50
5

Add this to the element of your web.config or App.config file. It will create a trace.log file in your project's bin/Debug folder. Or, you can specify an absolute path for the log file using the initializeData attribute.

  <system.diagnostics>
    <trace autoflush="true"/>
    <sources>
      <source name="System.Net" maxdatasize="9999" tracemode="protocolonly">
        <listeners>
          <add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="System.Net" value="Verbose"/>
    </switches>
  </system.diagnostics>

It warns that the maxdatasize and tracemode attributes are not allowed, but they increase the amount of data that can be logged, and avoid logging everything in hex.

Collin Anderson
  • 14,787
  • 6
  • 68
  • 57
0

If you work in a more restricted environment and don't have the luxury of using an application like Fiddler, you can do the following:

  1. Generate your web reference as usual.
  2. Write code to perform whatever web method call you're going to me.
  3. Create a new ASP .NET project of your choice, I went with MVC 4.
  4. Create a handler or controller/action, and extract the request stream like this:

using (var reader = new System.IO.StreamReader(Request.InputStream)) { result = reader.ReadToEnd(); }

  1. Put a breakpoint on it and run it in debug mode.
  2. On your client, set the Url of the SOAP request to your new controller/handler.
  3. Run your client. You should catch the breakpoint on your web app with your SOAP message.

It's not an ideal or pretty solution, but if you are working in a moderately restricted environment, it gets the job done.

Steven Hunt
  • 2,321
  • 19
  • 18
  • Sorry the information is not enough to be able to do this. I get the error that the HttpRequest does not have an InputStream. I probably configured the project in a different way, but no idea how to do it... – achecopar May 09 '22 at 16:37