Why does C#.Net Web service soap extension return empty stream when expecting text/xml?

I've implemented a simple soap extension to intercept the servers outgoing soap message stream and replace it with my own (code below).

Everything seems to work in the code step through except the server side afterserialize stage is called twice (2 times!)  but why?  It should only fire once.  In the second fire, afterserialize writes an empty stream to the output stream, but I already wrote to the output stream correctly on the first fire! My client says, expectedly, the above error, the service returns nothing==empty stream.

The only post I found with an answer was this one: http://www.thescripts.com/forum/thread375954.html but I don't think it applies in my case as I rewound the stream correctly (or at least I think I did).

I'm using vs 2005 and I'm running on XP pro. I'm using the embedded webserver in 2005 (cassini?). C#

As I said, everything appears to work fine.  On the server side It takes the input message, transforms it, and then writes it to the output stream.  I checked the stream it copies to the output (in afterserialize on the first time it fires) and everything looks hunky-dory.  However, the second time afterserialize fires everything is messed up and the streams are empty (appoutputstream and httpoutputstream).


all code below here
--*********************
Here is the soap extension (put in app_code folder)
--**********************

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

public class XmlStreamSoapExtension : System.Web.Services.Protocols.SoapExtension
    {
        bool output = false;
        Stream httpOutputStream;
        Stream chainedOutputStream;
        Stream appOutputStream;

        public override Stream ChainStream(Stream stream)
        {
            Stream result = stream;
            if ((output))
            {
                httpOutputStream = stream;
                chainedOutputStream = new MemoryStream();
                result = chainedOutputStream;
            }
            else
            {
                output = true;
            }
            return result;
        }

        public override object GetInitializer(System.Type serviceType)
        {
            return null;
        }

        public override object GetInitializer(System.Web.Services.Protocols.LogicalMethodInfo methodInfo, System.Web.Services.Protocols.SoapExtensionAttribute attribute)
        {
            return null;
        }

        public override void Initialize(object initializer)
        {
        }

        public override void ProcessMessage(SoapMessage message)
        {
            if (message.Stage == SoapMessageStage.AfterDeserialize)
            {
                HttpContext.Current.Request.InputStream.Position = 0;
                HttpContext.Current.Items["SoapInputStream"] = HttpContext.Current.Request.InputStream;
                appOutputStream = new MemoryStream();
                HttpContext.Current.Items["SoapOutputStream"] = appOutputStream;
            }
            else if (message.Stage == SoapMessageStage.AfterSerialize)
            {
                chainedOutputStream.Position = 0;
                XmlReader reader = new XmlTextReader(chainedOutputStream);
                reader.ReadStartElement("Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
                reader.MoveToContent();
                if ((reader.LocalName == "Header"))
                {
                    reader.Skip();
                }
                reader.ReadStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/");
                reader.MoveToContent();
                if ((reader.LocalName == "Fault" & reader.NamespaceURI == "http://schemas.xmlsoap.org/soap/envelope/"))
                {                    
                  chainedOutputStream.Position = 0;
                  CopyStream(chainedOutputStream, httpOutputStream);
                }
                else
                {
                    appOutputStream.Flush();
                    appOutputStream.Position = 0;
                    CopyStream(appOutputStream, httpOutputStream);                    
                }
                appOutputStream.Close();
            }
        }

        private void CopyStream(Stream src, Stream dest)
        {
            StreamReader reader = new StreamReader(src);
            StreamWriter writer = new StreamWriter(dest);
            writer.Write(reader.ReadToEnd());
            writer.Flush();
        }

    public class SoapStreams
    {

        public static Stream InputMessage
        {
            get
            {
                return ((Stream)(HttpContext.Current.Items["SoapInputStream"]));
            }
        }

        public static Stream OutputMessage
        {
            get
            {
                return ((Stream)(HttpContext.Current.Items["SoapOutputStream"]));
            }
        }
    }
    [AttributeUsage(AttributeTargets.Method)]
    public class XmlStreamSoapExtensionAttribute : SoapExtensionAttribute
    {
        private int m_priority;
        public override Type ExtensionType
        {
            get{ return typeof(XmlStreamSoapExtension);}
        }

        public override int Priority
        {
            get {return m_priority;}
            set{m_priority = 1;}
        }
    }

--*********************
end of soap extension class
--**********************

--*********************
--here is the arithmetic.cs file --all it does is a simple transform of the input message (also in app_code directory)  the asmx file only has one line of codes (a directive saying: <%@ WebService class="arithmetic" %>)
--*********************

using System;
using System.Web;
using System.Collections;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.Serialization;
[System.Web.Services.WebServiceBindingAttribute(Name = "Arithmetic", Namespace = "urn:msdn-microsoft-com:hows")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class arithmetic : WebService {

    private const string soapURI = "http://schemas.xmlsoap.org/soap/envelope/";
    private const string nsURI = "urn:msdn-microsoft-com:hows";

    [XmlStreamSoapExtension]
    [WebMethod]
    [SoapDocumentMethod("urn:msdn-microsoft-com:hows/Add", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Bare)]
    [return: XmlElement("AddResponse", Namespace = "urn:msdn-microsoft-com:hows")]
    public /* AddResponse */ void Add(/* Add Add1 */)
    {
        //************
        //this will transform the input stream (in a reader) directly to output stream
        //************

      XmlReaderSettings settings = new XmlReaderSettings();
        SoapStreams.InputMessage.Position = 0;
        XmlReader xReader = XmlReader.Create(new XmlTextReader(SoapStreams.InputMessage),settings);
        while (xReader.Read())
        {
        }
      XslCompiledTransform docXsl = new XslCompiledTransform();
        XsltSettings xslset = new XsltSettings();
        xslset.EnableDocumentFunction = true;
        string xslsource = "add.xslt";
        XmlUrlResolver resolver = new XmlUrlResolver();
        string pathxsl = HttpContext.Current.Server.MapPath(xslsource);
        docXsl.Load(new XmlTextReader(pathxsl), xslset, resolver);
        docXsl.Transform(xReader, new XmlTextWriter(SoapStreams.OutputMessage, System.Text.Encoding.UTF8));
    }
    public arithmetic () {
        //Uncomment the following line if using designed components
        //InitializeComponent();
    }

}

--***************************
--end of the arithmetic.cs file
--****************************


--**********************
--here is the xml message sent to the service by the client w/soap action of
--"urn:msdn-microsoft-com:hows/Add"
--which works fine as it fires the service up--you can just send this using Microsoft Fiddler
--************************

<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema'><soap:Body><Add xmlns='urn:msdn-microsoft-com:hows'><n1>10</n1><n2>20</n2></Add></soap:Body></soap:Envelope>

--*****************
--end of the input message
--*****************

--*****************
--here is the add.xslt file
--****************
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xsl:version="1.0">
<soap:Body>
<ns:AddResponse xmlns:ns="urn:msdn-microsoft-com:hows">
<ns:sum>
<xsl:value-of select="sum(//ns:Add/*/text())"/>
</ns:sum>
</ns:AddResponse>
</soap:Body>
</soap:Envelope>

--******************
--end of add.xslt file
--******************

--******************
--web config
--*******************

<webServices>
<soapExtensionTypes>
<add type="XmlStreamSoapExtension, App_Code" priority="1" group="High"/>
</soapExtensionTypes>
<protocols>
<remove name="HttpGet" />
<remove name="HttpPost" />
<remove name="Documentation" />
</protocols>
</webServices>

--*****************
--web config
--***************
tomandlisAsked:
Who is Participating?
 
Computer101Commented:
PAQed with points refunded (55)

Computer101
EE Admin
0
 
boy8964Commented:
your quesion is too long and proints are too short!
0
 
tomandlisAuthor Commented:
There, I increased the point value.  Sorry, but its all I've got and I've no inheritance to pass on.
0
 
tomandlisAuthor Commented:
Well, since nobody was up to the challenge (shame on you all) I had to figure it out myself.  The problem was in the configuration--I decorated the method with the soap extention, e.g., [XmlStreamSoapExtension] AND I also put it in the web.config under <soapextensiontypes>.  DOING BOTH IS BAD because of the following which I found at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconAlteringSOAPMessageUsingSOAPExtensions.asp

<clip>
If the SOAP extension is configured using an attribute, GetInitializer is called by the Web services infrastructure the first time a Web service method is accessed.

If the SOAP extension is configured in a configuration file, GetInitializer is called by the Web services infrastructure only the first time the entire Web service is accessed.
</clip>

If you do both GetInitializer is called twice (its not smart enough to know that isn't what you wanted) and thus you get parallel initializations and executions--one containing what you wanted to transmit from your webservice and the other containing nothing.  Unfortunately I was getting the nothing (or fortunately depending on how you look at it).

Once I removed either the method decoration attribute or the web.config soapextensiontype everything worked fine!  So there!

I'm hope this helps someone avoid 5 days of debugging (like what happened to me).
0
 
tomandlisAuthor Commented:
One other thing that tripped me up.  Remember, if you are using server side a soap extension then it will likely become your responsibility to write to the output stream.  I once tried to have the normal webservice pipeline serialize and return the output (during test) and I got an empty stream on the client side.  Duh!  you just wrote your stream to the chained output stream and your stream had nothing in it because your were trying to let the normal pipeline handle everything.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.