2

I'm trying to implement a SoapExtension in .NET that would compresses all soap traffic (both requests and responses). I have control over both the client and the server. I've started by copying this: http://www.mastercsharp.com/article.aspx?ArticleID=86&&TopicID=7 and slightly modifying it, and I have a problem.

I'm able to modify(zip) the request body when it's being sent from the client, it arrives at the server, the reverse operation(unzip) is fine too, BUT I can't seem to get the framework to reflect the changes I make to the stream object! On the server, I set the correct data to the newStream object but when the WebMethod is called, the arguments are still zipped (and thus invalid). Any idea why?

To make it easier for everybody including myself, I've dumbed down the SoapExtension to this:

using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.IO;
using System.Net;

// Define a SOAP Extension that traces the SOAP request and SOAP
// response for the XML Web service method the SOAP extension is
// applied to.

public class TraceExtension : SoapExtension {
Stream oldStream;
Stream newStream;
string filename;

// Save the Stream representing the SOAP request or SOAP response into
// a local memory buffer.
public override Stream ChainStream(Stream stream)
{
    oldStream = stream;
    newStream = new MemoryStream();
    return newStream;
}

// When the SOAP extension is accessed for the first time, the XML Web
// service method it is applied to is accessed to store the file
// name passed in, using the corresponding SoapExtensionAttribute.  
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
    return ((TraceExtensionAttribute)attribute).Filename;
}

// The SOAP extension was configured to run using a configuration file
// instead of an attribute applied to a specific XML Web service
// method.
public override object GetInitializer(Type WebServiceType)
{
    // Return a file name to log the trace information to, based on the
    // type.
    return "C:\\" + WebServiceType.FullName + ".log";
}

// Receive the file name stored by GetInitializer and store it in a
// member variable for this specific instance.
public override void Initialize(object initializer)
{
    filename = (string)initializer;
}

//  If the SoapMessageStage is such that the SoapRequest or
//  SoapResponse is still in the SOAP format to be sent or received,
//  save it out to a file.
public override void ProcessMessage(SoapMessage message)
{
    switch (message.Stage)
    {
        case SoapMessageStage.BeforeSerialize:
            break;
        case SoapMessageStage.AfterSerialize:
            WriteOutput(message);
            break;
        case SoapMessageStage.BeforeDeserialize:
            WriteInput(message);
            break;
        case SoapMessageStage.AfterDeserialize:
            break;
    }
}

public void WriteOutput(SoapMessage message)
{
    newStream.Position = 0;
    FileStream fs = new FileStream(filename, FileMode.Append,
        FileAccess.Write);
    StreamWriter w = new StreamWriter(fs);

    string soapString = (message is SoapServerMessage) ? "SoapResponse" : "SoapRequest";
    w.WriteLine("-----" + soapString + " at " + DateTime.Now);
    w.Flush();
    Copy(newStream, fs);
    w.Close();
    newStream.Position = 0;
    Copy(newStream, oldStream);
}

//public void WriteInput(SoapMessage message)
//{
//    Copy(oldStream, newStream);
//    FileStream fs = new FileStream(filename, FileMode.Append,
//        FileAccess.Write);
//    StreamWriter w = new StreamWriter(fs);

//    string soapString = (message is SoapServerMessage) ?
//        "SoapRequest" : "SoapResponse";
//    w.WriteLine("-----" + soapString +
//        " at " + DateTime.Now);
//    w.Flush();
//    newStream.Position = 0;
//    Copy(newStream, fs);
//    w.Close();
//    newStream.Position = 0;
//}


public void WriteInput(SoapMessage message)
{
    oldStream.Position = 0;
    StreamReader reader = new StreamReader(oldStream);
    StreamWriter writer = new StreamWriter(newStream);
    string data = reader.ReadToEnd();
    data = data.Replace("false", "true");
    writer.Write(data);
    writer.Flush();
}

void Copy(Stream from, Stream to)
{
    TextReader reader = new StreamReader(from);
    TextWriter writer = new StreamWriter(to);
    writer.WriteLine(reader.ReadToEnd());
    writer.Flush();
}

}

And it still won't work properly. When the soap request is received on the server (WriteInput method), all "false" values are changed to "true" and the data is saved to the newStream object, but it'll still show "false" values in the webmethod call!

My calls look like this: Client:

 [SoapDocumentMethodAttribute("http://Company.com/Product/admintool/webservices/SaveNativeRequest", RequestNamespace = "http://Company.com/Product/admintool/webservices/", ResponseNamespace = "http://Company.com/Product/admintool/webservices/", Use = SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
        [SoapHeaderAttribute("Ticket")]
        [TraceExtension(Priority = 0)]
        public void SaveNativeRequest(XmlNode nativeRequest, int? batchId)
        {
            this.Invoke("SaveNativeRequest", new object[] { nativeRequest, batchId });
        }

Server:

 [WebMethod]
    [TraceExtension(Priority = 0)]
    [SoapHeader("Ticket", Direction = SoapHeaderDirection.In)]
    public void SaveNativeRequest(XmlNode nativeRequest, int? batchId)
    {...}

Just for completeness, this is the attribute class

// Create a SoapExtensionAttribute for the SOAP Extension that can be
// applied to an XML Web service method.
using System;
using System.Web.Services.Protocols;
[AttributeUsage(AttributeTargets.Method)]
public class TraceExtensionAttribute : SoapExtensionAttribute
{
    private string filename = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\log.txt";
    private int priority;

    public override Type ExtensionType
    {
        get { return typeof(TraceExtension); }
    }

    public override int Priority
    {
        get { return priority; }
        set { priority = value; }
    }

    public string Filename
    {
        get
        {
            return filename;
        }
        set
        {
            filename = value;
        }
    }
}

I've been through this article: h**p://hyperthink.net/blog/inside-of-chainstream/ and I'm pretty sure I'm using the chainstream method correctly. I know there are other ways to do this but this really seemed like the clean option. I've read all sorts of articles on the topic, this http://msdn.microsoft.com/en-us/magazine/cc188761.aspx article inspired me with a workaround, I can use

(System.Web.HttpContext.Current.Items["RequestSoapContext"] as Microsoft.Web.Services3.SoapContext).Envelope.Body

in the ProcessMessage method to manipulate the soap body directly (this works fine, but is ugly)

Am I overlooking something obvious? Could some other part of the app be interfering with the stream chaining on the server? I'll be really really glad if anybody has any insights on this.

Filip
  • 51
  • 1
  • 4
  • My second recommendation with SoapExtensions is to first get the [example](http://msdn.microsoft.com/en-us/library/system.web.services.protocols.soapextension.aspx) working, then slowly turn the example into the SoapExtension you actually want. – John Saunders Apr 21 '11 at 19:49
  • Of course, my first recommendation is always to use WCF, so you can use extension mechanisms much better than SoapExtension. – John Saunders Apr 21 '11 at 19:50
  • The app isn't new and porting the whole monster to WFC is just not possible, sadly. To your second comment, making the example work would pretty much make the whole thing work, so, yeah :) – Filip Apr 21 '11 at 19:53
  • @Filip: no, I mean that the example _does_ work if you follow the directions. Getting it to work is just a matter of learning to follow the directions (web.config setup, for instance). _Then_ start changing it to become what you need. I did this with an extension to validate incoming (and outgoing!) messages. I was going crazy until I decided to go all the way back to the example. – John Saunders Apr 21 '11 at 19:57
  • No, it does not. Editing web.config is not necessary if the method attributes are provided. – Filip Apr 21 '11 at 19:58
  • @Filip: I know about the attributes, and what do you mean by "no, it does not"? – John Saunders Apr 21 '11 at 19:59
  • I mean the example I've provided doesn't work :) – Filip Apr 21 '11 at 20:00
  • @Filip: I have been talking about the example in the MSDN documentation. I posted the link. – John Saunders Apr 21 '11 at 20:03
  • Thanks! I'll definitely check it out – Filip Apr 21 '11 at 20:05
  • Oh, but sadly, this example doesn't modify the contents of the stream(s) . My code breaks as soon as I start doing exactly that. – Filip Apr 21 '11 at 20:13
  • @Filip: like I said, get the example to work, then start changing it. Your first change can be to have it change the stream. – John Saunders Apr 21 '11 at 20:15
  • well, getting the example to work is a matter of minutes. However, as soon as I change it to modify the newStream, it breaks: public void WriteInput(SoapMessage message) { oldStream.Position = 0; StreamReader reader = new StreamReader(oldStream); StreamWriter writer = new StreamWriter(newStream); string data = reader.ReadToEnd(); data = data.Replace("false", "true"); writer.Write(data); writer.Flush(); } I don't see how I can decompose this further – Filip Apr 21 '11 at 20:41
  • Well, then, that was quick. You've found the source of the problem. Please update your question so that others who are reading questions (but not comments) can see it and help. – John Saunders Apr 21 '11 at 20:54
  • I have not found a fix, I've only confirmed the example is correct and my change immediatelly breaks it. – Filip Apr 21 '11 at 20:56
  • I know you haven't found a fix. You have isolated the problem. If I were you, I'd replace the code you posted in the example with the code you changed to make the SoapExtension example break. That way we're all referring to the same code. – John Saunders Apr 21 '11 at 21:02
  • I see. I've updated the code. However, I don't believe I've truly isolated the problem, I already knew the issue is with modifying the stream but the underlying cause is still a mystery to me. – Filip Apr 21 '11 at 21:12

1 Answers1

0

I think you need to reset the position of the newStream in the WriteInput method.

So please add the following line at the end of the WriteInput method:

newStream.Position = 0;

It should solve your problem.

lxg
  • 12,375
  • 12
  • 51
  • 73