SoapExtension that allows you to insert XML into a SOAP header.
I work for a very large company with diverse technology solutions. My shop is .NET, but we're consuming a web service from another group that uses WebSphere and Java for their web services. When I tried to consume this web service from my ASP.NET application, I ran into some issues, because the web service expected a username and password security token, passed in the SOAP header. I assumed (gulp) that it'd be a small matter of programming (SMOP) to insert well-formed XML into a SOAP header. That's mostly right, but there is a complete dearth of documentation on how to do this. I'm from the Internets, and I'm here to help, folks.
Using the code
I'm consuming a Java web service from a WebSphere server. I have no problems creating the web reference and consuming the service, but apparently it expects more than what .NET sends it. Here's the header that comes out of my ASP.Net application:
. Body stuff here, with appropriate XML, passing parameters and values to
the web service...
Here's what the web service expects:
.Body stuff here, with appropriate XML, passing parameters and values to
the web service...
Let me be clear: this is an ASP.NET (C#) web form application, consuming a WebSphere web service. There is NO web method in the service for passing the username token. As I've illustrated above, the expectation is for this token (well-formed XML) to be inserted into the SOAP header. This is where the
SoapExtension comes to the rescue.
First, you need to create an extension class, one that inherits
SoapExtension. Something along the lines of:
public class myExtension : SoapExtension
By default, a SOAP extension has 5 overrides that you must provide:
GetInitializer (2 of them),
ChainStream are really the heart of the matter, and you'll see why in the code.
ChainStream provides an override to access the XML stream.
ProcessMessage provides an override for you to manipulate that stream. Inside
ProcessMessage, you'll need to test for the 4 stages of a
.AfterDeserialize. We're interested in what's being sent from the client to the server, so the
.AfterSerialize test is where we get the XML from cache, convert it to a string, do our manipulations, convert the string back out to a stream, and send it on it's merry way. Once this class is built, you need to perform one more very important step - make that extension available to your application. I have a web application, so I put this into the web.config file:
<add type="bberry1.myExtension,bberry1" priority="1" group="Low"/>
Note that my namespace is
bberry1, and the extension class is the brilliantly named "
myExtension." Once that's done, and you've rebuilt your project, you call your webservice, your overrides kick in, and you get what you want. Next, the class is constructed. I've placed all the code in a file named "customfilter.cs" in my project.
I mentioned several overrides above, and pointed out that
ProcessMessage were the crux of the issue. I've got a few declarations to make for this class first:
public class myExtension : SoapExtension
public bool outgoing = true;
public bool incoming = false;
private Stream outputStream;
public Stream oldStream;
public Stream newStream;
Next, I build the
public override Stream ChainStream(Stream stream)
this.outputStream = stream;
oldStream = stream;
newStream = new MemoryStream();
This makes the stream available to us for use in
ProcessMessage is where we see what phase of the whole SOAP stream we're in, and process accordingly. I can try to explain the gyrations here, but I think the code speaks for itself. One thing that bears explanation, however, is that I convert the outgoing stream to a string, add my stuff, then convert the string back out to the stream. This is all done in
SoapMessageStage.AfterSerialize. I still need for that stream to go back out to the web service, so I need to process that stream out down in
The process of converting the XML cache to string gave me quite the headache, as there was almost NO documentation on the Internets on how it's done (I'm a complete SOAP/XML newbie, mind you). As it turns out, it's pretty easy, and is done like this:
public string getXMLFromCache()
newStream.Position = 0; string strSOAPresponse = ExtractFromStream(newStream);
private String ExtractFromStream(Stream target)
if (target != null)
return (new StreamReader(target)).ReadToEnd();
ProcessMessage override in it's entirety:
public override void ProcessMessage(SoapMessage message)
XmlDocument xDoc = new XmlDocument();
String soapBodyString = getXMLFromCache();
String BodString =
int pos1 = soapBodyString.IndexOf(BodString) + BodString.Length;
int pos2 = soapBodyString.Length - pos1;
soapBodyString = soapBodyString.Substring(pos1, pos2);
soapBodyString = "<soap:Body>" + soapBodyString;
String xmlVersionString = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
String soapEnvelopeBeginString =
String soapEnvHeaderString =
String soapEnvHeaderString2 = "</Username><Password>";
String soapEnvHeaderString3 =
Stream appOutputStream = new MemoryStream();
StreamWriter soapMessageWriter = new StreamWriter(appOutputStream);
appOutputStream.Position = 0;
StreamReader reader = new StreamReader(appOutputStream);
StreamWriter writer = new StreamWriter(this.outputStream);
this.outgoing = false;
this.incoming = true;
readStr = new StreamReader(oldStream);
writeStr = new StreamWriter(newStream);
soapMsg1 = readStr.ReadToEnd();
soapMsg1 = xDoc.InnerXml;
newStream.Position = 0;
throw new Exception("invalid stage!");
Points of Interest
What's good about this little exercise is that I found out how completely I can control the information flowing back and forth between my client and the web service. It's really nice to know that Microsoft provides the
, but it would've been nicer to have a how-to like this one as part of the documentation.
- Aug 6th, 2007: Initial Release