[Last Call] Learn about multicloud storage options and how to improve your company's cloud strategy. Register Now

x
?
Solved

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

Posted on 2006-11-01
6
Medium Priority
?
16,603 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Preface This article introduces an authentication and authorization system for a website.  It is understood by the author and the project contributors that there is no such thing as a "one size fits all" system.  That being said, there is a certa…
I found this questions asking how to do this in many different forums, so I will describe here how to implement a solution using PHP and AJAX. The logical flow for the problem should be: Write an event handler for the first drop down box to get …
The viewer will learn the benefit of using external CSS files and the relationship between class and ID selectors. Create your external css file by saving it as style.css then set up your style tags: (CODE) Reference the nav tag and set your prop…
The viewer will learn how to create a basic form using some HTML5 and PHP for later processing. Set up your basic HTML file. Open your form tag and set the method and action attributes.: (CODE) Set up your first few inputs one for the name and …

656 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