Solved

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

Posted on 2006-11-01
6
16,058 Views
Last Modified: 2013-11-19
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
--***************
0
Comment
Question by:tomandlis
  • 3
6 Comments
 
LVL 4

Expert Comment

by:boy8964
ID: 17855891
your quesion is too long and proints are too short!
0
 

Author Comment

by:tomandlis
ID: 17856318
There, I increased the point value.  Sorry, but its all I've got and I've no inheritance to pass on.
0
 

Author Comment

by:tomandlis
ID: 17857142
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
 

Author Comment

by:tomandlis
ID: 17881430
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
 
LVL 1

Accepted Solution

by:
Computer101 earned 0 total points
ID: 18410340
PAQed with points refunded (55)

Computer101
EE Admin
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

It was really hard time for me to get the understanding of Delegates in C#. I went through many websites and articles but I found them very clumsy. After going through those sites, I noted down the points in a easy way so here I am sharing that unde…
International Data Corporation (IDC) prognosticates that before the current the year gets over disbursing on IT framework products to be sent in cloud environs will be $37.1B.
Viewers will learn one way to get user input in Java. Introduce the Scanner object: Declare the variable that stores the user input: An example prompting the user for input: Methods you need to invoke in order to properly get  user input:
Viewers will learn about if statements in Java and their use The if statement: The condition required to create an if statement: Variations of if statements: An example using if statements:

746 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now