HomeDigital EditionSys-Con RadioSearch Web Services Cd
B2B Beginning WS Business Process Management Case Studies Content Management Distributing Computing e-Business Electronic Data Interchange Enterprise Industry Insight Integration Interviews Java & Web Services .NET Portal Product Reviews Scalability & Performance Security SOAP Source Code UDDI Wireless WS Standards WS Tips & Techniques WSDL WS Editorials XML

Source Code for this article

As XML Web services invade the technology forefront, I almost feel as if an understanding of the SOAP protocol is becoming an everyday essential for me as a .NET developer.

Visual Studio .NET and the .NET Framework do a good job of hiding the details of low-level implementation of SOAP in Web services; however, my inner "SOAP sense" wasn't really satisfied with the little exposure to raw XML messages and SOAP protocol details I'd gotten while implementing Web services using Visual Studio .NET. So I began to implement my own SOAP Client to communicate with Web services while looking over the raw XML messages that are exchanged.

In this article I'll discuss how to build a generic SOAP Client using Visual Basic .NET. What I mean by "generic" is that this SOAP Client can be used to communicate with any Web service. I'll also discuss how to use this client to consume Web services. I chose UDDI (Universal Description, Discovery, and Integration) as a Web service to test my SOAP Client. Let's get started.

Anatomy of SOAP
SOAP is a general application-level protocol designed for application-to-application communication over any underlying transport protocol. The SOAP specification defines the XML formats for messages participating in a SOAP communication. If you have a well-formed XML document that conforms to SOAP specifications, you're ready to communicate with a Web service!

A SOAP message contains the following parts:

  • Envelope: The top-level container representing the message.
  • Header: An optional part of the SOAP message, usually used to carry data unrelated to the main application itself. For example, authentication information could be sent as a header in a SOAP message.
  • Body: Contains mandatory information intended for the ultimate message receiver.
In simple terms, communicating with a Web service is nothing more than formatting an XML document and sending it to the Web service server. Because the SOAP protocol is independent of the transport layer, you can implement the communication at any level, such as TCP/IP, HTTP, etc. For the purposes of this article I'm going to use HTTP as the transport layer for the SOAP communication.

Let's take a look at a sample SOAP message using HTTP, shown in Listing 1. As you can see, it looks like an HTTP-POST request. However, the main differences between a pure HTTP request and a SOAP request embedded in HTTP are the SOAP envelope payload and the SOAPAction header. With this background, let's move on to design a SOAP Client.

Designing a SOAP Client
Since I'm using HTTP as the transport layer for my SOAP Client, I need HTTP Request and Response objects for communication purposes. I'll define them in my class as shown below. (You can find these types in the namespace System.Net.)

Dim objHTTPReq As HttpWebRequest
Dim objHTTPRes As HttpWebResponse
The constructor for this class will create an instance of the HTTP Request class and bind to a specific network endpoint (the address of a Web service). So the class constructor expects a string as input, which is "Url" in the following code.

Public Sub New(ByVal Url As String)
objHTTPReq = _
CType(WebRequest.CreateDefault(New System.Uri(Url)), HttpWebRequest)

objHTTPReq.ContentType = _
"text/xml; charset=""utf-8"""
End Sub
Now that the basic class is ready, follow these steps to build a SOAP Client:
  • Set the HTTP and SOAP headers for the HTTP Request object.
  • Build the SOAP envelope around the SOAP body.
  • Attach credentials (if needed for the Web service) to the request.
  • Send the SOAP request over HTTP to the server.
  • Receive the SOAP response from the Web service.
Let's take a look at each of these steps in detail.

Set the HTTP and SOAP Headers (SetHeader)
I'm going to create a function called "SetHeader" to set various header values for the HTTP request. Even in pure HTTP communication there will be several headers associated with each HTTP request (such as "Method", "Accept", etc.). However, SetHeader will be used to set a SOAP-specific header called "SOAPAction", which is required for SOAP communication, along with the other generic HTTP headers. Since the SOAPAction header value is different for each Web service, you need to set it before communicating with any Web service.

As you can see from Listing 2, SetHeader accepts the header name and its value (name-value pairs). So when you add the SOAPAction header to your client, you need to pass both the SOAPAction literal and its value.

Build the SOAP Envelope (BuildEnv)
Now let's take a look at building the SOAP envelope and body. The SOAP body is specific to the Web service and depends on the parameters that the Web service expects, whereas the SOAP envelope is always in a standard format. BuildEnv (see Listing 3) accepts the SOAP body as a string and builds the standard SOAP envelope around it.

Attach Credentials (SetCred)
You can use SetCred (see Listing 4) from the SOAP Client class to set credentials that may be required by some Web services. For example, when you access the UDDI Publish API to publish your business/services you need to pass the credentials to invoke the Web services. In such cases SetCred comes in handy.

SetCred expects credentials (username and password) as input parameters, which it will attach to each HTTP request sent to the Web service.

Send the SOAP Request (Send)
Okay, it's time to write a function to send the actual SOAP request. The function "Send" in the SOAP Client class (see Listing 5) accepts the SOAP request message as a parameter and sends it over HTTP.

The SOAP message is simply written into the HTTP request stream using a StreamWriter class. The StreamWriter class can be found in the System.IO namespace.

Receive the SOAP Response (GetResponse)
The final step in building a SOAP Client is setting the "GetResponse" function. GetResponse is used to read the SOAP response back from the Web service. Since the response returned by the server is going to be another well-formatted XML document that conforms to the SOAP specification, I loaded the returned response into an XML DOM document to parse the SOAP body element.

The code in Listing 6 contains an interesting process. I'm trying to capture the response in the "Catch" block in case of an exception. This will help us in understanding what kind of exception occurred. I will discuss debugging SOAP applications in detail in the next section.

We've designed our SOAP Client. It's time to test it against a real Web service.

Testing the SOAP Client with a UDDI Web Service
If you're new to UDDI services, they're Web-based open directories run by various operators (such as Microsoft, IBM, etc.) that expose information about businesses, services, and related technical interfaces. By querying a UDDI service you can discover the company details and the technical details of the (Web) services they provide. For details about UDDI, you can refer to www.uddi.org.

You need to format the query to the UDDI service in a prespecified XML format before sending it to the UDDI server. The specifications for these XML formats are available at www.uddi.org/pubs/ProgrammersAPI-V2.04-Published-20020719.pdf.

Now let's say, for example, I want to query a UDDI service for a business. I need to use the "find_business" function XML format (refer to the previously mentioned API document). So if I want to find a business by the name "eSynaps", I have to format the query XML as shown below:

<find_business generic='1.0' xmlns='urn:uddi-org:api'>
<name>
esynaps
</name>
</find_business>
The above XML is going to be the SOAP body for the UDDI query. Let's move on to test the SOAP Client.

SOAPTest.ASPX
I've created a Web form, SOAPTest.ASPX (available from www.sys-con.com/ webservices/sourcec.cfm), to test the SOAP Client (see Figure 1). I hard-coded the vendors (Microsoft and IBM) into the UDDI Node dropdown list. You can choose one of the two nodes to test the SOAP Client, then select the UDDI function you want to invoke. Let's select the find_business function. Since I've already hard-coded the XML formats for all the UDDI query functions (from the UDDI API document), the XML query string format will appear in the query XML text box area of the Web form when you select a particular UDDI query function. (I've used SelectedIndexChanged event.) When you submit the form by clicking on the submit button, the "button_click" function will be executed from the code-behind page.

Figure 1

The button_click function actually instantiates the SOAP Client, sending and receiving the SOAP messages. Let's take a closer look at what's really happening in this function. First, declare the SOAP Client:

Dim objMySoap As MyComponents.MySOAPClient

Then create a new instance of the SOAP Client with the selected UDDI node from the Web form:

objMySoap = New MyComponents.MySOAPClient(node.SelectedItem.Value)

Set the HTTP and SOAP headers required to communicate with the Web service: "Method", "Accept", and "SOAPAction":

objMySoap.SetHeader("Method", "POST")
objMySoap.SetHeader("Accept", "text/xml;charset=""utf-8""")
objMySoap.SetHeader("SOAPAction", """""")
Now prepare the body of the SOAP message by building the envelope around it.

strQry = objMySoap.BuildEnv(query.Text)

Then send the XML message to the selected UDDI node using the SOAP Client's "Send" method, as shown below:

objMySoap.Send(strQry)

Receive the response from the SOAP Client by calling the GetResponse function and set it to the text area in the bottom portion of the Web form.

servOut.Text = objMySoap.GetResponse()

Now you'll see the response from the UDDI server. Figure 2 shows the XML being returned by the UDDI find_ business Web service.

Figure 2

Now let's revisit the topic of capturing the response stream from the HTTP Response object in the case of an exception. Even in case of a SOAP exception, the response stream will contain an XML message from the server with a SOAP Fault. This helps us greatly in understanding what went wrong in our communication with the Web service.

To simulate a SOAP Fault, I change the find_business query and submit the form again. The changed query and the Web service response are shown in Figure 3.

Figure 3

Changing the closing tag for the "name" element in the query XML creates an exception in the find_business Web service, causing the server to respond with a SOAP Fault message. To understand the details of the error, take a close look at the "dispositionReport". The "errInfo" element clearly states that I'm sending a malformed query XML to the Web service.

This way of capturing the response stream to read the dispositionReport in case of an exception really helps in debugging SOAP applications.

Summary
In this article you've seen how to design a SOAP Client using Visual Basic .NET and the .NET Framework. The SOAP in .NET supports only implementations over the HTTP protocol, so I chose the HTTP Request class to send the SOAP messages. You also saw the SOAP Client in action when I queried the UDDI node using the find_business Web service. You learned to set the appropriate headers to the SOAP request depending on which Web service you're going to invoke. Finally, I discussed how to debug your SOAP applications, using the disposition report from the SOAP Fault messages.

Now, since the SOAP Client is ready, you can use it against any Web service to send and receive SOAP messages to understand how the communication works and also to satisfy your "SOAP sense."

Author Bio
Chandu Thota, a technical lead for Click Commerce, Inc., is coauthor of Understanding the .NET Framework, from Wrox Press. Chandu is also the founder of www.eSynaps.com, an online .NET XML Web services portal. csthota@att.net

Designing a Generic Soap Client Using Visual Basic.Net, by Chandu Thota
WSJ Vol 02 Issue 08 - pg.14

	


Listing 1

POST /StockQuote HTTP/1.1
Host: www.stockquoteserver.com
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
SOAPAction: "Some-URI"

<Envelope
 xmlns="http://schemas.xmlsoap.org/soap/envelope/"
 encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <Body>
       <m:GetLastTradePrice xmlns:m="Some-URI">
           <symbol>DIS
       </m:GetLastTradePrice>
   </Body>
</Envelope>


Listing 2

Public Sub SetHeader(ByVal Name As String, ByVal Value As String)

Select Case (Name)
Case "Method"
    	objHTTPReq.Method = Value
Case "Accept"
      objHTTPReq.Accept = Value
Case Else
      objHTTPReq.Headers.Add(Name, Value)
End Select
End Sub



Listing 3

Public Function BuildEnv(ByVal Body As String) As String

BuildEnv = "" & _
"<Envelope xmlns='http://schemas.xmlsoap.org/soap/
   envelope/'>" & _
"<Body>" & Body & "</Body></Envelope>"

End Function


Listing 4

Public Sub SetCred(ByVal UName As String, ByVal UPass 
   As String)

Dim objCred As New Net.NetworkCredential(UName, UPass)

objHTTPReq.Credentials = objCred
objCred = Nothing

End Sub


Listing 5

Public Sub Send(ByVal Message As String)

Dim objStream As System.IO.StreamWriter

Try

objStream = New StreamWriter(objHTTPReq.GetRequestStream(), Encoding.UTF8)
objStream.Write(Message)
objStream.Close()

Catch

End Try

objStream = Nothing

End Sub



Listing 6

Public Function GetResponse() As String

Dim objXML As New System.Xml.XmlDocument()

Try

objHTTPRes = objHTTPReq.GetResponse()
objXML.Load(objHTTPRes.GetResponseStream())
GetResponse = objXML.DocumentElement.FirstChild.InnerXml.ToString

Catch e As WebException

objHTTPRes = e.Response
objXML.Load(objHTTPRes.GetResponseStream())
      GetResponse = objXML.OuterXml.ToString

End Try

objXML = Nothing

End Function

Additional Code for this Article

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.

  E-mail: info@sys-con.com

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.