Click here to Skip to main content
15,886,724 members
Articles / Web Development / ASP.NET
Article

SOAP Header Extensions How-To

Rate me:
Please Sign up or sign in to vote.
4.58/5 (15 votes)
6 Aug 2007CPOL4 min read 142.1K   33   49
Passing non-standard information in SOAP headers

Introduction

Create a SoapExtension that allows you to insert XML into a SOAP header.

Background

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:

XML
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:tns="http://tempuri.org/" 
xmlns:types="http://tempuri.org/encodedTypes" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
.
. Body stuff here, with appropriate XML, passing parameters and values to 
  the web service...
.

Here's what the web service expects:

XML
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Header>
<Security>
   <UsernameToken>
      <Username>XXXXXXX</Username>
      <Password>XXXXXXXXXXXXXXXXXXXX</Password>
   </UsernameToken>
</Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
.
.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:

C#
public class myExtension : SoapExtension

By default, a SOAP extension has 5 overrides that you must provide: ChainStream, GetInitializer (2 of them), Initialize, and ProcessMessage. ProcessMessage and 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 SoapMessage, namely .BeforeSerialize, .AfterSerialize, .BeforeDeserialize, and .AfterDeserialize. We're interested in what's being sent from the client to the server, so the SoapMessageStage.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:

XML
<webServices>
    <soapExtensionTypes>
        <add type="bberry1.myExtension,bberry1" priority="1" group="Low"/>
    </soapExtensionTypes>
</webServices>
</system.web>

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 ChainStream and ProcessMessage were the crux of the issue. I've got a few declarations to make for this class first:

C#
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 ChainStream override:

C#
public override Stream ChainStream(Stream stream)
{
// save a copy of the stream, create a new one for manipulating.
   this.outputStream = stream;
   oldStream = stream;
   newStream = new MemoryStream();
   return newStream;
}

This makes the stream available to us for use in ProcessMessage. 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 SoapMessageStage.BeforeDeserialize.

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:

C#
public string getXMLFromCache()
{
   newStream.Position = 0; // start at the beginning!
   string strSOAPresponse = ExtractFromStream(newStream);
   return strSOAPresponse;
}

Here's the ExtractFromStream function:

C#
private String ExtractFromStream(Stream target)
{
   if (target != null)
   return (new StreamReader(target)).ReadToEnd();
   return "";
}

Here's the ProcessMessage override in it's entirety:

public override void ProcessMessage(SoapMessage message)
{
   StreamReader readStr;
   StreamWriter writeStr;
   string soapMsg1;
   XmlDocument xDoc = new XmlDocument();
   // a SOAP message has 4 stages. We're interested in .AfterSerialize
   switch (message.Stage)
   {
      case SoapMessageStage.BeforeSerialize:
      break;
      case SoapMessageStage.AfterSerialize:
      {
      // Get the SOAP body as a string, so we can manipulate...
      String soapBodyString = getXMLFromCache();
      // Strip off the old header stuff before the message body
      // I'm not completely sure, but the soap:encodingStyle might be
      // unique to the WebSphere environment. Dunno.
      String BodString = 
"<soap:Body soap:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">";
      int pos1 = soapBodyString.IndexOf(BodString) + BodString.Length;
      int pos2 = soapBodyString.Length - pos1;
      soapBodyString = soapBodyString.Substring(pos1, pos2);
      soapBodyString = "<soap:Body>" + soapBodyString;
      // Create the SOAP Message 
      // It's comprised of a <soap:Element> that's enclosed in <soap:Body>. 
      // Pack the XML document inside the <soap:Body> element 
      String xmlVersionString = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
      String soapEnvelopeBeginString = 
      "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" 
          xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\" 
          xmlns:tns=\"http://tempuri.org/\" 
          xmlns:types=\"http://tempuri.org/encodedTypes\" 
          xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" 
          xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">";
      String soapEnvHeaderString = 
          "<soap:Header><Security><UsernameToken><Username>";
      String soapEnvHeaderString2 = "</Username><Password>";
      String soapEnvHeaderString3 = 
          "</Password></UsernameToken></Security></soap:Header>";
      Stream appOutputStream = new MemoryStream();
      StreamWriter soapMessageWriter = new StreamWriter(appOutputStream);
      soapMessageWriter.Write(xmlVersionString);
      soapMessageWriter.Write(soapEnvelopeBeginString);
      // The heavy-handed part - forcing the right headers AND the uname/pw :)
      soapMessageWriter.Write(soapEnvHeaderString);
      soapMessageWriter.Write("usernamestring");
      soapMessageWriter.Write(soapEnvHeaderString2);
      soapMessageWriter.Write("passwordstring");
      soapMessageWriter.Write(soapEnvHeaderString3);
      // End clubbing of baby seals
      // Add the soapBodyString back in - it's got all the closing 
      // XML we need.
      soapMessageWriter.Write(soapBodyString);
      // write it all out.
      soapMessageWriter.Flush();
      appOutputStream.Flush();
      appOutputStream.Position = 0;
      StreamReader reader = new StreamReader(appOutputStream);
      StreamWriter writer = new StreamWriter(this.outputStream);
      writer.Write(reader.ReadToEnd());
      writer.Flush();
      appOutputStream.Close();
      this.outgoing = false;
      this.incoming = true;
      break;
    }
   case SoapMessageStage.BeforeDeserialize:
   {
      // Make the output available for the client to parse...
      readStr = new StreamReader(oldStream);
      writeStr = new StreamWriter(newStream);
      soapMsg1 = readStr.ReadToEnd();
      xDoc.LoadXml(soapMsg1);
      soapMsg1 = xDoc.InnerXml;
      writeStr.Write(soapMsg1);
      writeStr.Flush();
      newStream.Position = 0;
      break;
   }
   case SoapMessageStage.AfterDeserialize:
   break;
   default:
   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 SoapExtension, but it would've been nicer to have a how-to like this one as part of the documentation.

History

  • Aug 6th, 2007: Initial Release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Epsilon
United States United States
Sr. Director at Epsilon, a leader in marketing technology.

Comments and Discussions

 
PraiseFor me too, exactly what I needed. Here is my small improvement Pin
quadpus21-Feb-18 1:45
quadpus21-Feb-18 1:45 
Questionthis.outputStream is null Pin
Member 1288840522-Mar-17 7:02
Member 1288840522-Mar-17 7:02 
QuestionExactly what I needed. I made a small improvement to the code to avoid the tricky string manipulation. Pin
blackstorm17-Dec-13 22:15
blackstorm17-Dec-13 22:15 
AnswerRe: Exactly what I needed. I made a small improvement to the code to avoid the tricky string manipulation. Pin
Jim Roth12-Feb-14 9:49
Jim Roth12-Feb-14 9:49 
GeneralMy vote of 1 Pin
johna@ocsx.com7-Oct-10 13:39
johna@ocsx.com7-Oct-10 13:39 
GeneralRe: My vote of 1 Pin
Jim Roth8-Nov-10 4:45
Jim Roth8-Nov-10 4:45 
GeneralHaving Problems Pin
lbloom30-Oct-08 7:45
lbloom30-Oct-08 7:45 
GeneralRe: Having Problems Pin
Jim Roth30-Oct-08 8:39
Jim Roth30-Oct-08 8:39 
QuestionMax Length for SOAP Message Pin
Greg Estes16-Sep-08 5:00
Greg Estes16-Sep-08 5:00 
AnswerRe: Max Length for SOAP Message Pin
Jim Roth16-Sep-08 5:45
Jim Roth16-Sep-08 5:45 
GeneralRe: Max Length for SOAP Message Pin
Greg Estes16-Sep-08 6:01
Greg Estes16-Sep-08 6:01 
GeneralRe: Max Length for SOAP Message Pin
Jim Roth16-Sep-08 6:22
Jim Roth16-Sep-08 6:22 
GeneralRe: Max Length for SOAP Message Pin
Greg Estes17-Sep-08 3:38
Greg Estes17-Sep-08 3:38 
GeneralRe: Max Length for SOAP Message Pin
Jim Roth17-Sep-08 3:55
Jim Roth17-Sep-08 3:55 
GeneralRe: Max Length for SOAP Message Pin
Greg Estes17-Sep-08 4:14
Greg Estes17-Sep-08 4:14 
GeneralRe: Max Length for SOAP Message Pin
Jim Roth17-Sep-08 4:25
Jim Roth17-Sep-08 4:25 
GeneralRe: Max Length for SOAP Message Pin
Greg Estes17-Sep-08 5:11
Greg Estes17-Sep-08 5:11 
GeneralRe: Max Length for SOAP Message Pin
Greg Estes17-Sep-08 10:39
Greg Estes17-Sep-08 10:39 
GeneralRe: Max Length for SOAP Message Pin
Jim Roth17-Sep-08 11:03
Jim Roth17-Sep-08 11:03 
GeneralRe: Max Length for SOAP Message Pin
Greg Estes17-Sep-08 12:50
Greg Estes17-Sep-08 12:50 
GeneralInteresting Pin
Dr.Luiji24-Apr-08 12:09
professionalDr.Luiji24-Apr-08 12:09 
QuestionWhat would I do without you.... Pin
deanders19-Mar-08 12:34
deanders19-Mar-08 12:34 
GeneralRe: What would I do without you.... Pin
deanders19-Mar-08 13:08
deanders19-Mar-08 13:08 
GeneralRe: What would I do without you.... Pin
Jim Roth20-Mar-08 3:36
Jim Roth20-Mar-08 3:36 
Questionpass username and password Pin
rzubi25-Jan-08 9:01
rzubi25-Jan-08 9:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.