DCSIMG
Instrumentation Tip for ASMX Web Services - David Sackstein's Blog

Instrumentation Tip for ASMX Web Services

This post describes a simple way to trace all method invocations on an ASMX Web Service. The source code for the demo can be downloaded here.

Problem

I am currently adding some features to a customer’s application that exposes its core functionality as an ASMX Web Service.

About every mistake in the book was made in the design of this product, so I was having a rough time understanding the API of the service, never mind the implementation. For a start, the methods are not strongly typed, mostly accepting strings as parameters, and there are over 20 of them! Oh, and some of the API functions have as many as 15 parameters!

In order to understand the API I decided to trace each of the method invocations by the client (which seems to know how to consume the service). For each call I will trace the name of the method and the values of each of its parameters.

As I couldnt stand the humiliation of writing a macro to create tens of lines of verbose tracing code in the service, I looked for a more elegant way of achieving this goal.

Solution 1: Migrate to WCF

The first solution that came to mind was to upgrade the ASMX service to WCF with a basicHttpBinding. Then, use the extensive tracing capabilities that come with WCF with just a few lines in the configuration file.

Upgrading to WCF is easy and I have every intention of doing this eventually, but if you are familiar with WCF tracing you might agree that this alone was not a compelling reason to do so now. From my experience, WCF tracing offers too much information for simple tasks and, what bothers me most is that in order to make constructive use of the information it generates you need to use a dedicated tool – SvcTraceViewer.

Solution 2: Soap Extensions

For ASMX Web Services, ASP.NET allows you to intercept SOAP messages on the server. You can catch incoming messages (operation invocations) just after they are deserialized and outgoing messages (operation return values) just before they are serialized.

This solution has two pieces:

  1. Define a class that derives from SoapExtension (named MethodTraceExtension). In the ProcessMessage method, handle incoming messages only. Extract the name and parameters of the method being invoked and trace this information.
  2. Configure the application (in web.config) to inject MethodTraceExtension on every web method.

In piece two, I will demonstrate three different ways to extract the information from the SoapMessage. You only need one way - the other two are for fun. For the tracing I will be using the tip from my last post.

Implementation

This is the MethodTraceExtension class. You can compile it and place it in a class library or you can place the source file in the App_Code folder:

The MethodTraceExtension class

namespace MethodTrace

{

    public class MethodTraceExtension : SoapExtension

    {

        #region Dont Care Overrides

 

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attr)

        {

            return attr;

        }

 

        public override object GetInitializer(Type obj)

        {

            return obj;

        }

 

        public override void Initialize(object initializer)

        {

        }

 

        #endregion

 

        public override void ProcessMessage(SoapMessage message)

        {

            // On the server side, this condition is true when processing an incoming message

 

            if (message.Stage == SoapMessageStage.AfterDeserialize)

            {

                Trace("Using Message Members", TraceFromMessageMembers(message));

 

                //Trace("Using Linq over SOAP", TraceFromSoapWithLinq(message));

                //Trace("Using DOM with XPath", TraceFromSoapWithXPath(message));

            }

        }

 

        private void Trace(string category, string message)

        {

            HttpContext.Current.Trace.Write (category, message);

        }

 

        private string TraceFromMessageMembers(SoapMessage message)

        {

            LogicalMethodInfo method = message.MethodInfo;

            StringBuilder sb = new StringBuilder();

 

            sb.AppendFormat("{0} (", method.Name);

 

            int paramCount = method.InParameters.Length;

 

            for (int i = 0; i < paramCount; i++)

            {

                sb.Append(method.InParameters[i].Name);

                sb.Append(" = ");

                sb.Append(message.GetInParameterValue(i).ToString());

 

                if (i != paramCount - 1)

                {

                    sb.Append(", ");

                }

            }

 

            sb.Append(")");

 

            return sb.ToString();

        }

    }

}

As you can see, its only the ProcessMessage function that requires a non-trivial implementation.

The TraceFromMessageMembers uses SoapMessage members to extract the name of the method and the values of its parameters and returns the information as a string. The Trace function uses HttpContext.Current.Trace.Write as described in the previous post.

Configuring the Soap Extension

If you want to apply the SoapExtension to methods selectively, you can use a SoapExtensionAttribute as follows:

Define an attribute that derives from SoapExtensionAttribute, and override its ExtensionType property to return typeof (MethodTraceExtension). Then apply this attribute to any method on which you would like to apply the soap extension.

But, in my case, I want to apply the soap extension to all methods – and, you will remember, I wanted to avoid writing repetetive code for no good reason, even it its just an attribute : )

So I applied the other option – configuration through web.config:

  <system.web>

    . . .

    <webServices>

      <soapExtensionTypes>

        <add type="MethodTrace.MethodTraceExtension, App_Code"/>

      </soapExtensionTypes>

    </webServices> 

    . . .

  </system.web>

This will apply the MethodTraceExtension to every Web Method in the service.

Dont forget to configure tracing as recommended in the previous post.

Thats it.

The client in the source code calls each method of the service. Every call is now traced to the Traces.txt file on the server.

In the rest of this post, I just want to show you two other methods you can use to extract the information we need from an incoming SoapMessage. They both rely on knowledge of the SOAP schema, which can change in subtle ways if a few attributes are changed on the service, so I would not recommend using them instead of the one above.

Still, I included them here for fun. One uses LINQ to XML, and the other use DOM and XPath to extract the information we need from the incoming SOAP message.

Other Methods to Analyze the Soap Message

This method extracts the information using LINQ to XML.

        private string TraceFromSoapWithLinq(SoapMessage message)

        {

            message.Stream.Position = 0;

 

            XmlReader reader = XmlReader.Create(message.Stream);

            XDocument doc = XDocument.Load(reader);

 

            var body =

                (from element in doc.Root.Elements()

                 where element.Name.LocalName == "Body"

                 select element).Single();

 

            StringBuilder sb = new StringBuilder();

 

            XElement methodElement = body.Elements().Single();

 

            sb.AppendFormat("{0} (", methodElement.Name.LocalName);

 

            var parameters =

                (from element in methodElement.Descendants()

                 select new {

                     Name = element.Name.LocalName,

                     Value = element.Value }).ToArray();

 

            foreach (var p in parameters)

            {

                sb.Append(p.Name);

                sb.Append(" = ");

                sb.Append(p.Value);

 

                if (p != parameters.Last())

                {

                    sb.Append(", ");

                }

            }

 

            sb.Append(")");

 

            return sb.ToString();

        }

and this method extracts the information using the DOM model and XPath.

        private string TraceFromSoapWithXPath(SoapMessage message)

        {

            message.Stream.Position = 0;

 

            XmlReader reader = XmlReader.Create(message.Stream);

            XmlDocument doc = new XmlDocument();

            doc.Load(reader);

 

            XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);

            nsm.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");

 

            XmlNode methodNode = doc.SelectNodes("// soap:Body/*", nsm)[0];

 

            StringBuilder sb = new StringBuilder();

 

            sb.AppendFormat("{0} (", methodNode.Name);

 

            XmlNodeList nodes = methodNode.ChildNodes;

 

            int paramCount = nodes.Count;

 

            for (int i = 0; i < paramCount; i++)

            {

                XmlNode node = nodes[i];

 

                sb.Append(node.Name);

                sb.Append(" = ");

                sb.Append(node.InnerText);

 

                if (i != paramCount - 1)

                {

                    sb.Append(", ");

                }

            }

 

            sb.Append(")");

            return sb.ToString();

        }

    }

Summary

In this post I demonstrated how to use a SoapExtension to trace every invocation of an ASMX Web Service in a centralized way.

I think that with this tool I will now find it easier to fathom the API of the service. I hope you find it useful too.

Published Friday, June 19, 2009 9:07 PM by David Sackstein

Comments

# re: Instrumentation Tip for ASMX Web Services

Saturday, June 20, 2009 1:11 AM by Yaron Naveh

There is another option which is to use WSE3 (web service enhancements).

This is an extension over asmx web services which adds a lot of capabilities. Among others it exposes a cleaner api to intercept the message pipeline (SoapFilter)

# re: Instrumentation Tip for ASMX Web Services

Saturday, June 20, 2009 1:10 PM by David Sackstein

Hi Yaron,

Thanks for your comment.

I preferred not to use WSE 3.0 because it isnt supported very well by Visual Studio 2008...

# re: Instrumentation Tip for ASMX Web Services

Saturday, June 20, 2009 5:28 PM by Yaron Naveh

I agree its UI is not supported by 2008 - but soap extensions have no UI at all...

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above:
Powered by Community Server (Commercial Edition), by Telligent Systems