.NET SoapExtension cannot modify soap request on the server

2.9k Views Asked by At

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.

1

There are 1 best solutions below

0
On

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.