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.
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.
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.
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
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com