SoapMessages are associated with their respective streams by the SoapServerProtocol class. Depending on whether the SoapMessage is a
The SoapServerProtocol class is responsible for calling SoapMessage.InitializeStreamChain(). InitializeStreamChain() is called from two separate places in SoapServerProtocol: once during SoapServerProtocol.Initialize() to handle the servicing of the request, and once during SoapServerProtocol.WriteReturnValues to handle the servicing of the response. InitializeStreamChain() takes an array of concrete SoapExtension instances, one for each extension that will process the message. Regardless of whether a request or reponse is being serviced, the current value of the message’s Stream property will always be first link the chain. When InitializeStreamChain() completes, the message’s Stream property will contain a pointer to the stream returned from the last SoapExtension in the processing chain. As a result, the dispatch code implemented in other parts of the WebServicesHandler doesn’t need to know anything about the existence of
The implementation of SoapMessage.InitializeStreamChain() looks something like the following:
internal void SoapMessageInitExtensionStreamChain(SoapExtension[] extensions)
{
int i;
}
} |
}
Individual
public virtual Stream ChainStream(Stream stream)
{
//This basically does nothing.
return stream;
}
Natively, this method doesn’t do much. It just returns the stream that was passed in, which doesn’t enable the extension to do much in terms of message processing. If the extension wants access to the contents of the message, it needs to cache a reference to the stream passed in to ChainStream() and return a new stream for other extensions to consume. A more useful implementation of ChainStream() looks like this:
public override Stream ChainStream(Stream stream)
{
this.oldStream = stream;
this.newStream = new MemoryStream();
return newStream;
}
Although this implementation of ChainStream() will work, it makes working with the chained streams more difficult than necessary. The reason for this has to do with when ChainStream() gets called by the framework during message processing. It’s actually called twice during the lifetime of a web method call – once just prior to the BeforeDeserialize phase, and once just prior to the BeforeSerialize phase. To compound the problem, the semantics of the oldStream and newStream variables are different depending upon which time ChainStream() is called. When ChainStream() is called prior to BeforeDeserialize, oldStream contains a readable stream containing the contents of the
Caching references to both the incoming argument to ChainStream() and the stream returned from ChainStream() is sufficient to enable message interception and alteration. However, the naïve implementation makes message processing more difficult, because of the changing semantics of newStream and oldStream. A better implementation of ChainStream() abstracts processing code from this semantic shift by using some conditional logic to clarify the situation (BTW, I found the basics of this implemention somewhere on the Net, but I can't for the life of me remember where right now):
public override Stream ChainStream(Stream stream)
{
//This flag will be set to true during the AfterDeserialize
//handler in ProcessMessage()
if( !bPostDeserialize)
{
//We’re deserializing the message, so stream is our input
//stream
this.inputStream = stream;
this.outputStream = new MemoryStream();
return outputStream;
}
else
{
//We’re serializing the message, so stream is the
//destination of our bits
this.inputStream = new MemoryStream();
this.outputStrem = stream;
return inputStream;
}
}
Implementing ChainStream in this way allows code that actually does the message processing to always be able to read from this.inputStream and write to this.outputStream, regardless of where in the message processing cycle the code currently is.
As if there were not enough streams in the picture already, the SoapMessage object passed as an argument to ProcessMessage() will itself contain a reference to a stream, exposed via the public read-only Stream property. It’s tempting to use the contents of this stream for processing, but this approach will not work in all cases. SoapMessage.Stream always points to the last stream in the processing chain, and as such should never be written to or read from. Doing so could potentially circumvent other extensions and will most likely result in an empty request being passed to the consumer. The reason for this is that other
