Using SOAP to Send Messages



Using SOAP to Send Messages

Earlier chapters of this book provided an overview of how to use the .NET Framework to send and receive SOAP messages, and you may want to refer to those chapters again for examples of how to send various kinds of messages using ASP.NET Web services. Here, I thought it would be enlightening to illustrate these and other features of the SOAP specification by building a generic SOAP engine with C# and managed code.

To begin with, let's build an object model that can deal with a SOAP Envelope class that will allow for reading and writing of SOAP envelopes which are both typed and untyped. By typed, I mean that we can use XML Serialization to create an object model to represent the elements found in the <Header> and <Body> section. By untyped, I mean that we can represent these same elements with the XmlElement class found in System.Xml.

Other SOAP APIs in .NET

With the Web Services Enhancements for Microsoft .NET (WSE), you also have access to a new API for SOAP messages that works similarly to this section's samples code: the Microsoft.Web.Services.SoapEnvelope. This class is based on untyped XML and offers more flexibility than this sample, at the expense of no typing.

Our Envelope class types the SOAP body as an object, and will set it to be an XmlElement if the various read methods don't specify the type of the body, meaning the object into which the body is serialized. The same goes for the array of headers. Listing 9.11 is the source for the Envelope class.

The Envelope Class
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Web.Services.Protocols;

namespace SOAPLibrary
{
     public class Envelope
     {
          //These private fields hold the actual values of
          //the header and body – notice that the  body is
          //of type Object, and the header is a
          //SoapHeaderCollection, which is found in
          //System.Web.Services.Protocols
          private SoapHeaderCollection _header =
                                      new SoapHeaderCollection();
          private Object _body;

          public Envelope(){}


          public SoapHeaderCollection Header
          {
               get
               {
                    return _header;
               }
          }
          public Object Body
          {
               get
               {
                    return _body;
               }
               set
               {
                    _body = value;
               }
          }

          //Write will create an XmlTextWriter based
          //on the stream passed in, and then serialize
          //any headers it finds in the header collection
          //as well as the body.
          //Notice that it is uses the GetType() method to
          //get the right types for the XmlSerializer.
          public void Write( Stream stream )
          {
               XmlTextWriter writer = new XmlTextWriter(
                           stream, new System.Text.UTF8Encoding() );
               writer.Formatting = Formatting.Indented;
               writer.WriteStartDocument();
                    writer.WriteStartElement("Envelope",
                       "http://schemas.xmlsoap.org/soap/envelope/");
                         if( _header.Count > 0 )
                         {
                           writer.WriteStartElement("Header");
                           foreach( SoapHeader header in _header )
                           {
                           XmlSerializer headerSer = new XmlSerializer( header.GetType() );
                           headerSer.Serialize( writer, header );
                           }
                           writer.WriteEndElement();
                         }
                         writer.WriteStartElement("Body");
                              if( _body.GetType() == typeof( System.Xml.XmlElement ) )
                              {
                                ((XmlElement)_body).WriteTo( writer );
                              }
                              else if( _body.GetType() == typeof( System.Xml.XmlReader ) )
                              {
                                 writer.WriteNode((XmlReader)_body, false );
                              }
                              else
                              {
                                 XmlSerializer ser = new XmlSerializer( _body.GetType() );
                                 ser.Serialize( writer, _body );
                              }
                         writer.WriteEndElement();
                    writer.WriteEndElement();
               writer.WriteEndDocument();
               writer.Close();
          }

          public void Read( Stream stream, Type type )
          {
               Read( stream, type, null );
          }

          public void Read( Stream stream,
                            Type type, Type[] headerTypes )
          {
               XmlTextReader reader = new XmlTextReader( stream );
               reader.ReadStartElement("Envelope", "http://schemas.xmlsoap.org/soap/envelope/");

               if( headerTypes != null )
               {
                    ReadKnownHeaders( reader, headerTypes );
               }
               else
               {
                    ReadUnknownHeaders( reader );
               }

               if( type != null )
               {
                    ReadKnownBody( reader, type );
               }
               else
               {
                    ReadUnknownBody( reader );
               }

               reader.ReadEndElement();
          }

          private void ReadUnknownHeaders( XmlTextReader reader )
          {
               while( reader.NodeType != XmlNodeType.Element )
               {
                    reader.Read();
               }

               if( reader.LocalName == "Header" )
               {
                    reader.ReadStartElement("Header");
                    reader.Read();
                    while( reader.NodeType != XmlNodeType.EndElement )
                    {
                         SoapUnknownHeader h = new
SoapUnknownHeader();
                         XmlDocument doc = new XmlDocument();
                         doc.LoadXml( reader.ReadOuterXml() );
                         h.Element = doc.DocumentElement;
                         _header.Add( h );
                         reader.Read();
                    }
                    reader.ReadEndElement();
               }
          }

          private void ReadKnownHeaders( XmlTextReader reader, Type[] headerTypes )
          {
               while( reader.NodeType != XmlNodeType.Element )
               {
                    reader.Read();
               }

               if( reader.LocalName == "Header" )
               {

                    reader.ReadStartElement("Header");
                    reader.Read();

                    XmlSerializer[] sers = XmlSerializer.FromTypes( headerTypes );
                    foreach( XmlSerializer s in sers )
                    {
                         if( s.CanDeserialize( reader ) )
                         {
                              _header.Add( (SoapHeader)s.Deserialize( reader ) );
                         }
                         reader.Read();
                    }
                    reader.ReadEndElement();
               }
          }

          private void ReadKnownBody( XmlTextReader reader, Type type )
          {
               while( reader.NodeType != XmlNodeType.Element )
               {
                    reader.Read();
               }

               reader.ReadStartElement("Body");
               XmlSerializer ser = new XmlSerializer( type );
               _body = ser.Deserialize( reader );
               reader.ReadEndElement();
          }

          private void ReadUnknownBody( XmlTextReader reader )
          {
               while( reader.NodeType != XmlNodeType.Element )
               {
                    reader.Read();
               }

               reader.ReadStartElement("Body");
               reader.Read();
               String s = reader.ReadOuterXml();
               XmlDocument d = new XmlDocument();
               d.LoadXml( s );
               _body = d.DocumentElement;
               reader.ReadEndElement();
          }

          public void Read( Stream stream )
          {
               Read( stream, null, null );
          }
     }
}

Now, to use this class, we need to write and read streams that include XML in them. Listing 9.12 is a collection of static methods that allow us to read and write the <Envelope> as a SOAP message.

The Message-Sending API
using System;
using System.Net;
using System.IO;

namespace SOAPLibrary
{
     public class SoapMessagingClient
     {

          public static Envelope SendHttpRequest(
                        Envelope env,
                        String url,
                        String action )
          {
               return SendHttpRequest(
                              env, url, action, null );
          }

       public static Envelope SendHttpRequest( Envelope env, String url, String action, Type bodyType )
       {
        HttpWebRequest wr = (HttpWebRequest)WebRequest.Create( url );
        wr.Headers.Add("SOAPAction", action);
        wr.Method = "POST";
        Stream s = wr.GetRequestStream();
        SendRequest( env, s );

        WebResponse response = wr.GetResponse();
        return ReceiveRequest( response.GetResponseStream(), bodyType );
       }

       public static void SendRequest( Envelope env, Stream stream )
       {
        env.Write( stream );
        stream.Close();
       }

       public static Envelope ReceiveRequest( Stream stream, Type bodyType )
       {
        Envelope env = new Envelope();
        env.Read( stream, bodyType );
        stream.Close();
        return env;
       }
       public static Envelope ReceiveRequest( Stream stream )
       {
         return ReceiveRequest( stream, null );
       }
     }
}

Now you can easily use these classes to send messages that are in fact SOAP messages. For example, imagine we have a Book class that we want to be able to send, as shown in Listing 9.13.

A Typical Class Representing a Message Body
public class Book
{
     private DateTime _pubdate;
     public String Title;
     public String Author;
     public int Pagecount;
     public String PubDate
     {
        get
        {
         return _pubdate.ToShortDateString();
        }
        set
        {
         _pubdate = Convert.ToDateTime(value);
        }
     }
}

It's easy to read and write this class using the static methods. The code to write out the envelope is shown in Listing 9.14.

Using the Envelope and Messaging Sending Classes
Book book = new Book();
book.Author = "Hervey Wilson";
book.Pagecount = 400;
book.PubDate = "12/12/03";
book.Title = "Programming like a god.";
Envelope env = new Envelope();
env.Body = book;
fs = new FileStream( "c:\\soap.xml", FileMode.CreateNew );
SoapMessagingClient.SendRequest( env, fs );

To read in an envelope is just as simple:

ffs = new FileStream( "c:\\soap.xml", FileMode.Open ); 
Envelope env2 = SoapMessagingClient.ReceiveRequest( ffs );
Console.WriteLine( ((XmlElement)env2.Body).InnerXml );

In this last bit of code, we aren't specifying the type of the body, so it is read in as an XmlElement. The output on the command line shows us the XML for this code:

<Title>Programming like a god</Title> 
<Author>Hervey Wilson</Author>
<Pagecount>400</Pagecount>
<PubDate>12/12/2003</PubDate>