k10n
Jim Klopfenstein's Radio Weblog

 



Subscribe to "k10n" in Radio UserLand.

Click to see the XML version of this web page.

Click here to send an email to the editor of this weblog.
 
 

SoapExtension Walkthrough -- Command Line Version

Follow these steps to create an XML web service, a client test program, and a SoapExtension class that logs and obfuscates the messages that pass between them.  For this walkthrough, you must have installed the Microsoft .NET Framework, a free upgrade to Windows available from Microsoft.

Tasks Detailed steps
1. Create a solution space
  1. Create an empty directory called Demo in the IIS document space (i.e. under inetpubwwwroot).
  2. Create an empty directory called bin directly under the Demo directory.
  3. Make the demo virtual root an application.
2. Create a simple Web Service
  1. Create a file in the Demo directory called Echo.asmx with the following content:
    <%@ WebService Language="c#" Class="Echo" %>
    using System;
    using System.Web;
    using System.Web.Services;
    public class Echo : WebService
    {
      [WebMethod] public string YouSaid(string whatyousaid)
      {
        return “You said “ + whatyousaid;
      }
    } 
  2. Browse to http://localhost/Demo/echo.asmx?wsdl to view the WSDL for this new Web service.
3. Create a test program for the Web Service
  1. Create a proxy class source file for the Web service with the WSDL command-line tool:
    wsdl http://localhost/Echo.asmx?wsdl
  2. Compile the proxy class:
    csc /t:module Echo.cs
  3. Create a class with a Main function to call the Web service through the proxy:
    using System;
    public class Test
    {
      public static void Main(string [] args)
      {
        Echo e = new Echo();
        Console.WriteLine(e.YouSaid("Hello there!"));
      }
  4. Build the test program, linking in the proxy class:
    csc /t:exe /addmodule:Echo.netmodule Test.cs
  5. Run the test program; the console window should show the message You said Hello there!
4. Create an empty SoapExtension
  1. Create a new C# source file called SampleSoapExtension.cs with the following content:
    using System;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.IO;
    namespace SampleSoapExtension
    {
      public class SampleSoapExtensionClass : SoapExtension
      {
        public override Stream ChainStream(Stream stream)
        {
          return stream;
        }
        public override object GetInitializer(Type serviceType)
        {
          return null;
        }
        public override object GetInitializer(LogicalMethodInfo methodInfo,SoapExtensionAttribute attribute)
        {
          return null;
        }
        public override void Initialize(object initializer)
        {
        }
        public override void ProcessMessage(System.Web.Services.Protocols.SoapMessage message)
        {
        }
      }
    } 
  2. Compile the class into a dll:
    csc /t:library /out:binSampleSoapExtension.dll SampleSoapExtension.cs
5. Create an attribute to apply the extension to the Web Service
  1. Add the following class to SampleSoapExtension:
    [AttributeUsage(AttributeTargets.Method)]
    public class SampleSoapExtensionAttribute : SoapExtensionAttribute
    {
      private int priority;
      public override Type ExtensionType
      {
        get
        {
          return typeof(SampleSoapExtensionClass);
        }
      }
      public override int Priority
      {
        get
        {
          return priority;
        }
        set
        {
          priority = value;
        }
      }
    }
  2. Rebuild the DLL.
6. Apply the SoapExtension to the Web Service
  1. Add an ASP.NET Assembly directive specifying the namespace to Echo.asmx:
    <%@ Assembly Name="SampleSoapExtension" %> 
  2. Add a using directive referencing the SampleSoapExtension namespace to Echo.asmx:
    using SampleSoapExtension; 
  3. Add the decoration [SampleSoapExtension] to the method YouSaid.
  4. Run Test.exe (no need to rebuild). It should work exactly as it did before.
7. Add logging to the SoapExtension
  1. Add the logMessage method to the SampleSoapExtension class.
    private void logMessage(string header,string message)
    {
      FileStream fs = new FileStream("c:\\logs\\log.txt", FileMode.Append,FileAccess.Write);
      StreamWriter w = new StreamWriter(fs);
      w.WriteLine("--- " + header);
      w.WriteLine(message);
      w.Flush();
      w.Close();
    } 
  2. Add an invocation of logMessage(“Initialize”,””);
  3. Make sure that the logs directory exists and that Everyone has write access to it.
  4. Build the DLL and run the test program. You should have a log file with a single entry. This proves that the soap extension is being invoked.
8. Apply the SoapExtension to the client (Test) program
  1. Create a new text file, named Test.exe.config
  2. Add the following lines to this file
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
     <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <probing privatePath="bin"/>
      </assemblyBinding>
     </runtime>
     <system.web>
      <webServices>
       <soapExtensionTypes>
        <add type="SampleSoapExtension.SampleSoapExtensionClass, sampleSoapExtension" priority="0" group="1"/>
       </soapExtensionTypes>
      </webServices>
     </system.web>
    </configuration>
  3. Delete the log.txt file from the logs directory.
  4. Run the test program (no need to build anything). This time there will be two entries in the log.txt file—one from the client and one from the server
9. Make the SoapExtension actually do something
  1. Add two private member variables to the SampleSoapExtensionClass:
    private Stream inwardStream;
    private Stream outwardStream;
  2. Replace the method body of ChainStream with:
    outwardStream = stream;
    inwardStream = new MemoryStream();
    return inwardStream;
  3. Add the following method body to ProcessMessages:
    StreamReader r;
    StreamWriter w;
    switch (message.Stage)
    {
      case SoapMessageStage.BeforeDeserialize:
        logMessage("BeforeDeserialize",message.GetType().ToString());
        r = new StreamReader(outwardStream);
        w = new StreamWriter(inwardStream);
        w.Write(r.ReadToEnd());
        w.Flush();
        inwardStream.Position = 0;
        break;
      case SoapMessageStage.AfterSerialize:
        logMessage("AfterSerialize",message.GetType().ToString());
        inwardStream.Position = 0;
        r = new StreamReader(inwardStream);
        w = new StreamWriter(outwardStream);
        w.Write(r.ReadToEnd());
        w.Flush();
        break;
    }
  4. Delete the log file, then build the dll and run the test program. You will see the following progression of calls:
    Initialize
    AfterSerialize with a type of ClientSoapMessage
    Initialize
    BeforeDeserialize with a type of ServerSoapMessage
    AfterSerialize with a type of ServerSoapMessage
    BeforeDeserialize with a type of ClientSoapMessage
10. Add the SOAP messages to the log file
  1. In the ProcessMessages method of the SampleSoapExtensionClass, add two member variables:
    string whatami;
    string soapText;
  2. In the same method, delete the calls to logMessages
  3. Add the following line at the start of the BeforeDeserialize case:
    whatami = (message is SoapClientMessage) ? "Response to Client" : "Request to Server"; 
  4. Add the following line at the start of the AfterSerialize case:
     whatami = (message is SoapClientMessage) ? "Request from Client" : "Response from Server"; 
  5. Replace the line
    w.write(r.ReadToEnd());
    in both cases with:
    soapText = r.ReadToEnd();
    logMessage(whatami,soapText);
    w.Write(soapText);
  6. Delete the log file, then rebuild the dll and run the test program. You will see the same progression of calls as in 9 above, but with clearer descriptions and the actual SOAP messages
11.
  1. Add the following helper functions to the SampleSoapExtensionClass:
    private void bumpup(ref StringBuilder instr)
    {
      for (int i=0; i<instr.Length; i++)
        if (instr[i] >= 'a' && instr[i] < 'z')
          instr[i] = Convert.ToChar(Convert.ToByte(instr[i])+1);
        else if (instr[i] == 'z')
          instr[i] = 'a';
    }
    private void bumpdown(ref StringBuilder instr)
    {
      for (int i=0; i<instr.Length; i++)
        if (instr[i] > 'a' && instr[i] <= 'z')
          instr[i] = Convert.ToChar(Convert.ToByte(instr[i])-1);
        else if (instr[i] == 'a')
          instr[i] = 'z';
    } 
  2. At the top of the file, add
    using System.Text;
  3. Replace the declaration of soapText with
    StringBuilder sb;
  4. Replace the three lines you added in 10e above in both cases with:
    sb = new StringBuilder();
    sb.Append(r.ReadToEnd());
    logMessage("Unmodified "+whatami,sb.ToString());
    logMessage("Modified "+whatami,sb.ToString());
    w.Write(sb.ToString()); 
  5. In the BeforeDeserialize case, add
    bumpup(ref sb);
    between the two logMessage calls. In the AfterSerialize case, add
    bumpdown(ref sb);
  6. Delete the log file, then rebuild the dll and run the test program. You will see a pair of messages, one unmodified, one ummodified in place of the SOAP messages in 10 above. This demonstrates that obfuscated messages are being passed between the client and the server.


Click here to visit the Radio UserLand website. © Copyright 2003 Jim Klopfenstein.
Last update: 2/10/2003; 8:43:42 AM.