31

Consider the requirement to log incoming SOAP requests to an ASP.NET ASMX web service. The task is to capture the raw XML being sent to the web service.

The incoming message needs to be logged for debug inspection. The application already has its own logging library in use, so the ideal usage would be something like this:

//string or XML, it doesn't matter.
string incomingSoapRequest = GetSoapRequest();

Logger.LogMessage(incomingSoapRequest);
  • Are there any easy solutions to capture the raw XML of the incoming SOAP requests?
  • Which events would you handle to get access to this object and the relevant properties?
  • Is there anyway IIS can capture the incoming request and push to a log?
p.campbell
  • 98,673
  • 67
  • 256
  • 322

4 Answers4

23

You can also implement by placing the code in Global.asax.cs

protected void Application_BeginRequest(object sender, EventArgs e)
{
    // Create byte array to hold request bytes
    byte[] inputStream = new byte[HttpContext.Current.Request.ContentLength];

    // Read entire request inputstream
    HttpContext.Current.Request.InputStream.Read(inputStream, 0, inputStream.Length);

    //Set stream back to beginning
    HttpContext.Current.Request.InputStream.Position = 0;

    //Get  XML request
    string requestString = ASCIIEncoding.ASCII.GetString(inputStream);

}

I have a Utility method in my web service that I use to capture the request when something happens that I am not expecting like a unhandled exception.

    /// <summary>
    /// Captures raw XML request and writes to FailedSubmission folder.
    /// </summary>
    internal static void CaptureRequest()
    {
        const string procName = "CaptureRequest";

        try
        {
            log.WarnFormat("{0} - Writing XML request to FailedSubmission folder", procName);

            byte[] inputStream = new byte[HttpContext.Current.Request.ContentLength];

            //Get current stream position so we can set it back to that after logging
            Int64 currentStreamPosition = HttpContext.Current.Request.InputStream.Position;

            HttpContext.Current.Request.InputStream.Position = 0;

            HttpContext.Current.Request.InputStream.Read(inputStream, 0, HttpContext.Current.Request.ContentLength);

            //Set back stream position to original position
            HttpContext.Current.Request.InputStream.Position = currentStreamPosition;

            string xml = ASCIIEncoding.ASCII.GetString(inputStream);

            string fileName = Guid.NewGuid().ToString() + ".xml";

            log.WarnFormat("{0} - Request being written to filename: {1}", procName, fileName);

            File.WriteAllText(Configuration.FailedSubmissionsFolder + fileName, xml);
        }
        catch
        {
        }

    }

Then in web.config I store several AppSetting values that define what level I want to use to capture the request.

    <!-- true/false - If true will write to an XML file the raw request when any Unhandled exception occurrs -->
    <add key="CaptureRequestOnUnhandledException" value="true"/>

    <!-- true/false - If true will write to an XML file the raw request when any type of error is returned to the client-->
    <add key="CaptureRequestOnAllFailures" value="false"/>

    <!-- true/false - If true will write to an XML file the raw request for every request to the web service -->
    <add key="CaptureAllRequests" value="false"/>

Then in my Application_BeginRequest I have it modified like so. Note that Configuration is a static class I create to read properties from web.config and other areas.

    protected void Application_BeginRequest(object sender, EventArgs e)
    {

        if(Configuration.CaptureAllRequests)
        {
            Utility.CaptureRequest();
        }
    }
Jim Scott
  • 2,493
  • 1
  • 18
  • 16
20

One way to capture the raw message is to use SoapExtensions.

An alternative to SoapExtensions is to implement IHttpModule and grab the input stream as it's coming in.

public class LogModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += this.OnBegin;
    }

    private void OnBegin(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;
        HttpContext context = app.Context;

        byte[] buffer = new byte[context.Request.InputStream.Length];
        context.Request.InputStream.Read(buffer, 0, buffer.Length);
        context.Request.InputStream.Position = 0;

        string soapMessage = Encoding.ASCII.GetString(buffer);

        // Do something with soapMessage
    }

    public void Dispose()
    {
        throw new NotImplementedException();
    }
}
nivlam
  • 3,223
  • 4
  • 30
  • 39
  • That won't work very well with UTF-8 encoding, I don't think. Also, are you sure the stream is seekable? Always? – John Saunders Apr 12 '10 at 20:05
  • For the input stream, yes. This is what I'm currently using to debug incoming requests and I haven't encountered any problems yet. The response stream however was not seekable. I had to wrap the response stream (original/copy) before I could log that. – nivlam Apr 12 '10 at 20:10
  • 1
    what about the output stream ?? how do I capture that using an IHttpModule implementation ? – gillyb Feb 08 '11 at 12:06
  • 1
    this gives me soapMessage="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" – hakan Oct 14 '16 at 13:15
  • Do not *throw* an exception in Dispose! It would be far better to leave it as an empty method that does nothing. Also see: https://stackoverflow.com/a/577671/29762 – Ryan May 11 '22 at 19:54
14

You know that you dont actually need to create a HttpModule right?

You can also read the contents of the Request.InputStream from within your asmx WebMethod.

Here is an article I wrote on this approach.

Code is as follows:

using System;
using System.Collections.Generic;
using System.Web;
using System.Xml;
using System.IO;
using System.Text;
using System.Web.Services;
using System.Web.Services.Protocols;

namespace SoapRequestEcho
{
  [WebService(
  Namespace = "http://soap.request.echo.com/",
  Name = "SoapRequestEcho")]
  public class EchoWebService : WebService
  {

    [WebMethod(Description = "Echo Soap Request")]
    public XmlDocument EchoSoapRequest(int input)
    {
      // Initialize soap request XML
      XmlDocument xmlSoapRequest = new XmlDocument();

      // Get raw request body
      Stream receiveStream = HttpContext.Current.Request.InputStream;

      // Move to beginning of input stream and read
      receiveStream.Position = 0;
      using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
      {
        // Load into XML document
        xmlSoapRequest.Load(readStream);
      }

      // Return
      return xmlSoapRequest;
    }
  }
}
atconway
  • 20,624
  • 30
  • 159
  • 229
Steven de Salas
  • 20,944
  • 9
  • 74
  • 82
-2

There are no easy ways to do this. You will have to implement a SoapExtension. The example at the previous link shows an extension that can be used to log the data.

If you had been using WCF, then you could simply set the configuration to produce message logs.


According to Steven de Salas, you can use the Request.InputStream property within the webmethod. I have not tried this, but he says that it works.

I would want to test this with both http and https, and with and without other SoapExtensions running at the same time. These are things that might affect what kind of stream the InputStream is set to. Some streams cannot seek, for instance, which might leave you with a stream positioned after the end of the data, and which you cannot move to the beginning.

Community
  • 1
  • 1
John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • 2
    -1 There is an easy way to do this. Just use Request.InputStream inside your WebMethod. – Steven de Salas Oct 12 '11 at 12:52
  • Have you tried this? Is that a seekable stream? If not, then the stream will already have been read to the end by the time the webmethod is called. – John Saunders Oct 12 '11 at 13:05
  • 1
    Yup, it works, you can enforce Stream.Position = 0 but dont actually need to during the SOAP request. – Steven de Salas Oct 12 '11 at 16:21
  • I'm very surprised. I have worked with SoapExtension. In that case, the stream you are given is not seekable. Rather than downvoting, you should add your comment as an answer. – John Saunders Oct 12 '11 at 18:33
  • 2
    I added a post (see above) before voting down this one. Reason why I voted down is because I dont want people to think that doing this is actually hard. The Request.InputStream is available at the stage when the WebMethod is called during a standard SOAP request and can be accessed without issues. I guess that its quite possible that in other contexts it may not be accessible or 'seekable' but that is not what the question is after. – Steven de Salas Oct 13 '11 at 08:56