dot.NET.Framework.Essentials.1002003,.3Ed [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

dot.NET.Framework.Essentials.1002003,.3Ed [Electronic resources] - نسخه متنی

Hoang Lam; Thuan L. Thai

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید










6.4 Web Services Consumers


Now
that you have successfully created a
web service, let's take a look at how this web
service is used by web clients. Web services clients communicate with
web services through standard web protocols. They send and receive
XML-encoded messages to and from the web services. This means any
application on any platform can access the web services as long as it
uses standard web protocols and understands the XML-encoded messages.
As mentioned earlier, there are three protocols that the web clients
can employ to communicate with the servers (web services): HTTP GET,
HTTP POST, and SOAP. We demonstrate next how to build client
applications that utilize each of these protocols. These web
services-client applications are done in legacy languages such as VB6
and Perl,[9] and .NET languages, such as C# and VB.NET, to demonstrate
the cross-language/cross-platform benefits of web services.

[9] We use SOAP::Lite PERL modules. See
http://www.soaplite.com/.



6.4.1 HTTP GET Consumer


Let's look at how it is done using

HTTP GET first, since it is the simplest.
In the examples that follow, we use
localhost
as the name of the web server running the service and
PubsWS as the virtual directory. If you have
deployed the sample web service on a remote server,
you'll need to substitute the name of the server and
virtual directory as appropriate.

If you point your web browser at the web service URL
(http://localhost/PubsWS/PubsWS.asmx), it will
give you a list of supported methods. To find out more about these
methods, click one of them. This brings up a default web service
consumer. This consumer, autogenerated through the use of reflection,
is great for testing your web services'
methods.[10] It uses the HTTP GET
protocol to communicate with the web service. This consumer features
a form that lets you test the method (see Figure 6-3), as well as descriptions of how to access the
method via SOAP, HTTP GET, or HTTP POST.

[10] A simple reflection example can be found in
Section 4.3.1.3.



Figure 6-3. An autogenerated web services consumer


Here is the description of the GET request and response supplied by
the default consumer:

The following is a sample HTTP GET request and response. The
placeholders shown need to be replaced with actual values.
GET /PubsWS/PubsWS.asmx/GetAuthor?sSSN=string HTTP/1.1
Host: localhost
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<DataSet xmlns="http://Oreilly/DotNetEssentials/">
<schema xmlns="http://www.w3.org/2001/XMLSchema">schema</schema>xml
</DataSet>

Using HTTP GET protocol, the complete URL to invoke the web method,
along with parameters, can be the following:

http://localhost/PubsWS/PubsWS.asmx/GetAuthor?sSSN=172-32-1176

Here is the response; including HTTP response headers and the raw XML
(note how the response includes the serialized schema and data from
the DataSet object):

Cache-Control: private, max-age=0
Date: Tue, 08 May 2001 20:53:16 GMT
Server: Microsoft-IIS/5.0
Content-Length: 2450
Content-Type: text/xml; charset=utf-8
Client-Date: Tue, 08 May 2001 20:53:16 GMT
Client-Peer: 127.0.0.1:80
<?xml version="1.0" encoding="utf-8"?>
<DataSet xmlns="http://Oreilly/DotNetEssentials/">
<xs:schema id="NewDataSet"
xmlns="
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="SelectedAuthor">
<xs:complexType>
<xs:sequence>
<xs:element name="au_id" type="xs:string"
minOccurs="0" />
<xs:element name="au_lname" type="xs:string"
minOccurs="0" />
<xs:element name="au_fname" type="xs:string"
minOccurs="0" />
<xs:element name="phone" type="xs:string"
minOccurs="0" />
<xs:element name="address" type="xs:string"
minOccurs="0" />
<xs:element name="city" type="xs:string"
minOccurs="0" />
<xs:element name="state" type="xs:string"
minOccurs="0" />
<xs:element name="zip" type="xs:string"
minOccurs="0" />
<xs:element name="contract" type="xs:boolean"
minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<diffgr:diffgram
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<NewDataSet xmlns=">
<SelectedAuthor diffgr:id="SelectedAuthor1" msdata:rowOrder="0">
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Johnson</au_fname>
<phone>408 496-7223</phone>
<address>10932 Bigge Rd.</address>
<city>Menlo Park</city>
<state>CA</state>
<zip>94025</zip>
<contract>true</contract>
</SelectedAuthor>
</NewDataSet>
</diffgr:diffgram>
</DataSet>


6.4.2 HTTP POST Consumer


In the section "HTTP GET Consumer,"
we saw the automatic creation of a web services consumer by merely
hitting the URL of the web services,
http://localhost/PubsWS/PubsWS.asmx. It is now
time for us to see how a web client can use HTTP POST and SOAP to
access a web service. This time around, we are going write a C# web
service consumer.

The Microsoft .NET SDK has a rich set of tools to simplify the
process of creating or consuming web services. We are going to use
one of these tools, wsdl, to generate source code
for the proxies to the actual web services:[11]

[11] wsdl.exe generates the proxy source code
similar to the way IDL compilers generate source files for DCOM
proxies. The only difference is that WSDL is the language that
describes the interface of the software component, which is
XML-based.


wsdl /l:CS /protocol:HttpPost http://localhost/PubsWS/PubsWS.asmx?WSDL

This command line creates a proxy for the PubsWS web service from the
WSDL document from the URL
http://localhost/PubsWS/PubsWS.asmx?WSDL. The
proxy uses HTTP POST as its protocol to talk to the web service; it
is generated as a C# source file. The wsdl tool
can also take a WSDL file as its input instead of a URL pointing to
the location where the WSDL can be obtained.

This C# proxy source file represents the proxy class for the PubsWS
web service that the clients can compile against. This generated C#
file contains a proxy class PubsWS that derives from
HttpPostClientProtocol class. If you use the /protocol:HttpGet or
/protocol:SOAP2[12]
parameters, the PubsWS derives from either the HttpGetClientProtocol
or SoapHttpClientProtocol class.

[12] In .NET Framework 1.1, SOAP 1.2 is
also supported via /protocol:SOAP12. All this does is generate the
SOAP derived proxy for SOAP Version 1.2.


After generating the C# source file PubsWS.cs,
we have two choices for how this proxy can be used. One way is to
include this source file in the client application project using
Visual Studio .NET. The project has to be a C# project if you choose
this route.[13] To make use of the proxy, you also have
to add to your project any references that the proxy depends on. In
this example, the necessary references for the proxy file are
System.Web.Services, System.Web.Services.Protocols,
System.Xml.Serialization, and System.Data.

[13] For other languages, use wsdl with the /l
option to specify the language. See Appendix D for more
details.


The other way to use the proxy is more flexible. You can compile the
C# source file into a dynamic link library (DLL) and
then add a reference to this DLL to any project you want to create.
This way you can even have a VB project use the DLL.

Below is the command line used to compile the C# proxy source into a
DLL. Notice that the three references are linked to
PubsWS.cs so that the resulting
PubsWS.DLL is self-contained (type the entire
command on one line):

csc /t:library
/r:system.web.services.dll
/r:system.xml.dll
/r:system.data.dll
PubsWS.cs

Regardless of how you choose to use the proxy, the client application
code will still look the same. Consider the next two code examples
containing C# and VB code. For both languages, the first lines create
an instance of the proxy to the web service, PubsWS. The second lines
invoke the GetAuthors web method to get a DataSet as the result. The
remaining lines bind the default view of the table
Authors to the data grid, add the data grid to a
form, and display the form. Note that these examples use the Windows
Forms API, which we'll discuss in Chapter 8. Here is the C# web service client,
TestProxy.cs:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Data;
public class TestProxy
{
public static void Main( )
{
/* Create a proxy. */
PubsWS oProxy = new PubsWS( );
/* Invoke GetAuthors( ) over HTTPPOST and get the data set. */
DataSet oDS = oProxy.GetAuthors( );
/* Create a data grid and connect it to the data set. */
DataGrid dg = new DataGrid( );
dg.Size = new Size(490, 270);
dg.DataSource = oDS.Tables["Authors"].DefaultView;
/* Set the properties of the form and add the data grid. */
Form myForm = new Form( );
myForm.Text = "DataGrid Sample";
myForm.Size = new Size(500, 300);
myForm.Controls.Add(dg);
/* Display the form. */
System.Windows.Forms.Application.Run(myForm);
}
}

If you created the DLL as previously directed, you can compile this
with the following command:

csc TestProxy.cs /r:PubsWS.dll

This creates the executable TestProxy.exe, which
gets a DataSet using a HTTP POST call, and displays a data grid
containing that dataset. Figure 6-4 shows the
output of the C# client after obtaining the data from the PubsWS web
service via HTTP POST protocol.


Figure 6-4. C# web service client after calling GetAuthors( )


Here is the VB web service client,
TestProxyVB.vb:

imports System
imports System.Drawing
imports System.Windows.Forms
imports System.Data
Module TestProxyVB
Sub Main( )
' Create a proxy.
dim oProxy as PubsWS = new PubsWS( )
' Invoice GetAuthors( ) over SOAP and get the data set.
dim oDS as DataSet = oProxy.GetAuthors( )
' Create a data grid and connect it to the data set.
dim dg as DataGrid = new DataGrid( )
dg.Size = new Size(490, 270)
dg.DataSource = oDS.Tables("Authors").DefaultView
' Set the properties of the form and add the data grid.
dim myForm as Form = new Form( )
myForm.Text = "DataGrid Sample"
myForm.Size = new Size(500, 300)
myForm.Controls.Add(dg)
' Display the form.
System.Windows.Forms.Application.Run(myForm)
End Sub
End Module

You can compile the VB web service client with this command (type the
entire command on one line):

vbc TestProxyVB.vb
/r:System.Drawing.dll
/r:System.Windows.Forms.dll
/r:System.Data.dll
/r:PubsWS.dll
/r:System.Web.Services.dll
/r:System.dll
/r:System.xml.dll

Instead of using wsdl to generate and include the
proxy in your application, you can also rely on VS.NET to automate
the whole process. In VS.NET, you can just add a Web Reference to
your application. The process of adding a Web Reference to an
application involves the discovery of the web service, obtaining the
WSDL, generating the proxy, and including the proxy into the
application.[14]

[14] You can find the proxy source file under
Web References\ReferenceName as reference.cs (if
you're working with C#). If you have not renamed the
reference name, it is localhost by default. (You
might have to select the option to "show all
files" in VS.NET Solution Explorer.)



6.4.3 Non-.NET Consumers


This section
shows how to develop non-.NET web service consumers using HTTP GET,
HTTP POST,
and SOAP protocols. Because we cannot just create the proxy class
from the WSDL and compile it with the client code directly, we must
look at the WSDL file to understand how to construct and interpret
messages between the web service and the clients. We trimmed down the
WSDL file for our PubsWS web service to show only types, messages,
ports, operations, and bindings that we actually use in the next
several web service-client examples. In particular, we will have our
VB6 client access the following:


Web method


Protocol


GetBooks( )


HTTP GET protocol


GetAuthor(ssn)


HTTP POST protocol


GetBooksByAuthor(ssn)


SOAP protocol

As a reference, here is the simplified version of the WSDL file while
you experiment with the VB6 client application:

<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns: . . .
xmlns:s0="http://Oreilly/DotNetEssentials/"
targetNamespace="http://Oreilly/DotNetEssentials/" >
<types>
<!-- This data type is used by the HTTP POST call -->
<s:element name="GetAuthor">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
name="sSSN" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<!-- This data type is used by the HTTP POST call -->
<s:element name="GetAuthorResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
name="GetAuthorResult">
<s:complexType>
<s:sequence>
<s:element ref="s:schema" />
<s:any />
</s:sequence>
</s:complexType>
</s:element>
</s:sequence>
</s:complexType>
</s:element>
<!-- This data type is used by the SOAP call -->
<s:element name="GetBooksByAuthor">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
name="sAuthorSSN" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<!-- This data type is used by the SOAP call -->
<s:element name="GetBooksByAuthorResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
name="GetBooksByAuthorResult">
<s:complexType>
<s:sequence>
<s:element ref="s:schema" />
<s:any />
</s:sequence>
</s:complexType>
</s:element>
</s:sequence>
</s:complexType>
</s:element>
<!-- This data type is used by the HTTP GET call -->
<s:element name="GetBooks">
<s:complexType />
</s:element>
<!-- This data type is used by the HTTP GET call -->
<s:element name="GetBooksResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
name="GetBooksResult">
<s:complexType>
<s:sequence>
<s:element ref="s:schema" />
<s:any />
</s:sequence>
</s:complexType>
</s:element>
</s:sequence>
</s:complexType>
</s:element>
<!-- This data type is used by the HTTP GET/POST responses -->
<s:element name="DataSet"
<s:complexType>
<s:sequence>
<s:element ref="s:schema" />
<s:any />
</s:sequence>
</s:complexType>
</s:element>
</types>
<!-- These messages are used by the SOAP call -->
<message name="GetBooksByAuthorSoapIn">
<part name="parameters" element="s0:GetBooksByAuthor" />
</message>
<message name="GetBooksByAuthorSoapOut">
<part name="parameters" element="s0:GetBooksByAuthorResponse" />
</message>
<!-- These messages are used by the HTTP GET call -->
<message name="GetBooksHttpGetIn" />
<message name="GetBooksHttpGetOut">
<part name="Body" element="s0:DataSet" />
</message>
<!-- These messages are used by the HTTP POST call -->
<message name="GetAuthorHttpPostIn">
<part name="sSSN" type="s:string" />
</message>
<message name="GetAuthorHttpPostOut">
<part name="Body" element="s0:DataSet" />
</message>
<!-- SOAP port -->
<portType name="PubsWSSoap">
<operation name="GetBooks">
<documentation>Find books by author's SSN.</documentation>
<input name="GetBooksByAuthor"
message="s0:GetBooksByAuthorSoapIn" />
<output name="GetBooksByAuthor"
message="s0:GetBooksByAuthorSoapOut" />
</operation>
</portType>
<!-- HTTP GET port -->
<portType name="PubsWSHttpGet">
<operation name="GetBooks">
<input message="s0:GetBooksHttpGetIn" />
<output message="s0:GetBooksHttpGetOut" />
</operation>
</portType>
<!-- HTTP POST port -->
<portType name="PubsWSHttpPost">
<operation name="GetAuthor">
<input message="s0:GetAuthorHttpPostIn" />
<output message="s0:GetAuthorHttpPostOut" />
</operation>
</portType>
<!-- SOAP binding -->
<binding name="PubsWSSoap" type="s0:PubsWSSoap">
<soap:binding
transport="http://schemas.xmlsoap.org/soap/http"
style="document" />
<operation name="GetBooks">
<soap:operation
soapAction="http://Oreilly/DotNetEssentials/GetBooksByAuthor"
style="document" />
<input name="GetBooksByAuthor">
<soap:body use="literal" />
</input>
<output name="GetBooksByAuthor">
<soap:body use="literal" />
</output>
</operation>
</binding>
<!-- HTTP GET binding -->
<binding name="PubsWSHttpGet" type="s0:PubsWSHttpGet">
<http:binding verb="GET" />
<operation name="GetBooks">
<http:operation location="/GetBooks" />
<input>
<http:urlEncoded />
</input>
<output>
<mime:mimeXml part="Body" />
</output>
</operation>
</binding>
<!-- HTTP POST binding -->
<binding name="PubsWSHttpPost" type="s0:PubsWSHttpPost">
<http:binding verb="POST" />
<operation name="GetAuthor">
<http:operation location="/GetAuthor" />
<input>
<mime:content type="application/x-www-form-urlencoded" />
</input>
<output>
<mime:mimeXml part="Body" />
</output>
</operation>
</binding>
<!-- The whole web service and address bindings -->
<service name="PubsWS">
<port name="PubsWSSoap" binding="s0:PubsWSSoap">
<soap:address location="http://localhost/PubsWS/PubsWS.asmx" />
</port>
<port name="PubsWSHttpGet" binding="s0:PubsWSHttpGet">
<http:address location="http://localhost/PubsWS/PubsWS.asmx" />
</port>
<port name="PubsWSHttpPost" binding="s0:PubsWSHttpPost">
<http:address location="http://localhost/PubsWS/PubsWS.asmx" />
</port>
</service>
</definitions>

In both the HTTP GET and HTTP POST protocols, you pass parameters to
the web services as name/value pairs. With the HTTP GET protocol, you
must pass parameters in the query string, whereas the HTTP POST
protocol packs the parameters in the body of the request package. To
demonstrate this point, we will construct a simple VB client using
both HTTP GET and HTTP POST protocols to communicate with the PubsWS
web service.

Let's first create a VB6 standard application. We
need to add a reference to Microsoft XML, v3.0
(msxml3.dll), because we'll use
the XMLHTTP object to help us communicate with the web services. For
demonstrative purposes, we will also use the Microsoft Internet
Controls component (shdocvw.dll) to display XML
and HTML content.

First, add two buttons on the default form, form1,
and give them the captions GET and
POST, as well as the names
cmdGet and cmdPost,
respectively. After that, drag the WebBrowser object from the toolbar
onto the form, and name the control myWebBrowser.
If you make the WebBrowser navigate to about:blank
initially, you will end up with something like Figure 6-5.


Figure 6-5. VB client form to test web services


Now all we need is some code similar to the following to handle the
two buttons' click events:

Private Sub cmdGet_Click( )
Dim oXMLHTTP As XMLHTTP
Dim oDOM As DOMDocument
Dim oXSL As DOMDocument
' Call the web service to get an XML document
Set oXMLHTTP = New XMLHTTP
oXMLHTTP.open "GET",_
"http://localhost/PubsWS/PubsWS.asmx/GetBooks", _
False
oXMLHTTP.send
Set oDOM = oXMLHTTP.responseXML
' Create the XSL document to be used for transformation
Set oXSL = New DOMDocument
oXSL.Load App.Path & "\templateTitle.xsl"
' Transform the XML document into an HTML document and display
myWebBrowser.Document.Write CStr(oDOM.transformNode(oXSL))
myWebBrowser.Document.Close
Set oXSL = Nothing
Set oDOM = Nothing
Set oXMLHTTP = Nothing
End Sub
Private Sub cmdPost_Click( )
Dim oXMLHTTP As XMLHTTP
Dim oDOM As DOMDocument
Dim oXSL As DOMDocument
' Call the web service to get an XML document
Set oXMLHTTP = New XMLHTTP
oXMLHTTP.open "POST", _
"http://localhost/PubsWS/PubsWS.asmx/GetAuthor", _
False
oXMLHTTP.setRequestHeader "Content-Type", _
"application/x-www-form-urlencoded"
oXMLHTTP.send "sSSN=172-32-1176"
Set oDOM = oXMLHTTP.responseXML
' Create the XSL document to be used for transformation
Set oXSL = New DOMDocument
oXSL.Load App.Path & "\templateAuthor.xsl"
' Transform the XML document into an HTML document and display
myWebBrowser.Document.Write oDOM.transformNode(oXSL)
myWebBrowser.Document.Close
Set oXSL = Nothing
Set oDOM = Nothing
Set oXMLHTTP = Nothing
End Sub

The two subroutines are similar in structure, except that the first
one uses the HTTP GET protocol and the second one uses the HTTP POST
protocol to get to the PubsWS web service. Let's
take a closer look at what the two subroutines do.

For the HTTP GET protocol, we use the XMLHTTP object to point to the
URL for the web method, as specified in the WSDL. Since the GetBooks
web method does not require any parameters, the query string in this
case is empty. The method is invoked synchronously because the async
parameter to XMLHTTP's open
method is set to false. After the method
invocation is done, we transform the XML result using
templateTitle.xsl and display the HTML on the
myWebBrowser instance on the form. Figure 6-6 displays the screen of our web services
testing application after invoking the GetBooks web method at URL
http://localhost/PubsWS/ PubsWS.asmx/ through
HTTP GET protocol.


Figure 6-6. VB client form after calling GetBooks


For the HTTP POST protocol, we also point the XMLHTTP object to the
URL for the web methodin this case, method GetAuthor. Because
this is a POST request, we have to specify in the HTTP header that
the request is coming over as a form by setting the
Content-Type header variable to
application/x-www-form-urlencoded. If this
variable is not set, XMLHTTP by default passes the data to the server
in XML format.

Another difference worth noticing is that the GetAuthor method
requires a single parameter, which is the SSN of the author as a
string. Since this is a post request, we are going to send the
name/value pair directly to the server in the body of the message.
Because the Content-Type header has been set to
application/x-www-form-urlencoded, the server will
know how to get to the parameters and perform the work requested.
This time, we use templateAuthor.xsl to
transform the XML result to HTML and display it. Figure 6-7 shows our application after invoking the
GetAuthor web method of PubsWS web service through HTTP POST
protocol.


Figure 6-7. VB client form after calling GetAuthor


The following code is the XSL used to transform the XML result from
the GetBooks web method call to HTML to be displayed on the web
browser instance on the VB form:

<html version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<head><title>A list of books</title></head>
<style>
.hdr { background-color=#ffeedd; font-weight=bold; }
</style>
<body>
<B>List of books</B>
<table style="border-collapse:collapse" border="1">
<tr>
<td class="hdr">Title</td>
<td class="hdr">Type</td>
<td class="hdr">Price</td>
<td class="hdr">Notes</td>
</tr>
<xsl:for-each select="//Books">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="type"/></td>
<td><xsl:value-of select="price"/></td>
<td><xsl:value-of select="notes"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>

Here is the XSL used to transform the XML result from the GetAuthor
web method call to HTML to be displayed on the web browser instance
on the VB form:

<html version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<head><title>Selected author</title></head>
<STYLE>
.hdr { background-color:'#ffeedd';
text-align:'right'; vertical-align:'top';
font-weight=bold; }
</STYLE>
<body>
<B>Selected author</B>
<xsl:for-each select="//SelectedAuthor">
<table style="border-collapse:'collapse'" border="1">
<tr><td class="hdr">ID</td>
<td><xsl:value-of select="au_id"/></td></tr>
<tr><td class="hdr">Name</td>
<td><xsl:value-of select="au_fname"/>
<xsl:value-of select="au_lname"/></td></tr>
<tr><td class="hdr">Address</td>
<td><xsl:value-of select="address"/><br>
<xsl:value-of select="city"/>,
<xsl:value-of select="state"/>
<xsl:value-of select="zip"/></br></td></tr>
<tr><td class="hdr">Phone</td>
<td><xsl:value-of select="phone"/></td></tr>
</table>
</xsl:for-each>
</body>
</html>

We can also use SOAP protocol to access the web service. Because the
web service is exposed through HTTP and XML, any clients on any
platform can access the service as long as they conform to the
specification of the service. Again, this specification is the WSDL
file. By inspecting the WSDL filespecifically, the SOAP
sectionwe can use XMLHTTP again to communicate in SOAP dialog.
Let's see how this can be done.

Let's go back to the example of consumer web
services using VB6 and XMLHTTP. Add another button on the form, and
call it cmdSOAP with caption
SOAP. This time, we will ask the web service to
return all books written by a particular author:

Private Sub cmdSOAP_Click( )
Dim oXMLHTTP As XMLHTTP
Dim oDOM As DOMDocument
Dim oXSL As DOMDocument
' Call the web service to get an XML document
Set oXMLHTTP = New XMLHTTP
oXMLHTTP.open "POST", "http://localhost/PubsWS/PubsWS.asmx", False
Dim sBody As String
sBody = " & _
"<soap:Envelope" & _
" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"" & _
" xmlns:xsd="http://www.w3.org/2001/XMLSchema"" & _
" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">" & _
"<soap:Body>" & _
"<GetBooksByAuthor xmlns="http://Oreilly/DotNetEssentials/">" & _
"<sAuthorSSN>213-46-8915</sAuthorSSN>" & _
"</GetBooksByAuthor>" & _
"</soap:Body>" & _
"</soap:Envelope>"
oXMLHTTP.setRequestHeader "Content-Type", "text/xml"
oXMLHTTP.setRequestHeader "SOAPAction",
"http://Oreilly/DotNetEssentials/GetBooksByAuthor"
oXMLHTTP.send sBody
Set oDOM = oXMLHTTP.responseXML
' Create the XSL document to be used for transformation
Set oXSL = New DOMDocument
oXSL.Load App.Path & "\templateAuthorTitle.xsl"
' Transform the XML document into an HTML document
myWebBrowser.Document.Write oDOM.transformNode(oXSL)
myWebBrowser.Document.Close
Set oXSL = Nothing
Set oDOM = Nothing
Set oXMLHTTP = Nothing
End Sub

This method is structurally similar to the ones used for HTTP GET and
HTTP POST; however, it has some very important differences. In SOAP,
you have to set the Content-Type to
text/xml instead of
application/x-www-form-urlencoded as for the HTTP
POST. By this time, it should be clear to you that only HTTP POST and
SOAP care about the Content-Type because they send
the data in the body of the HTTP request. The HTTP GET protocol does
not really care about the Content-Type because all
of the parameters are packaged into the query string. In addition to
the difference in format of the data content, you also have to refer
to the WSDL to set the SOAPAction header variable
to the call you want. Looking back at the SOAP section of the WSDL,
if you want to call the GetBooks(sAuthorSSN)
method of the web service, you will set the
SOAPAction header variable to
http://Oreilly/DotNetEssentials/GetBooksByAuthor.
On the other hand, if you want to call the GetBooks( ) method
instead, the SOAPAction variable has to be set to
http://Oreilly/DotNetEssentials/GetBooks. The
reason the namespace is
http://Oreilly/DotNetEssentials/ is because we set
it up as the attribute of the PubsWS web service class.

After setting up the header variables, pass the parameters to the
server in the body of the message. While HTTP POST passes the
parameters in name/value pairs, SOAP passes the parameters in a
well-defined XML structure:

<soap:Envelope . . . namespace omitted . . . >
<soap:Body>
<GetBooksByAuthor xmlns="http://Oreilly/DotNetEssentials/">
<sAuthorSSN>213-46-8915</sAuthorSSN>
</GetBooksByAuthor>
</soap:Body>
</soap:Envelope>

Both the SOAP request and response messages are packaged within a
Body inside an Envelope. With
the previously specified request, the response SOAP message looks
like this:

<?xml version="1.0"?>
<soap:Envelope . . . namespace omitted . . . >
<soap:Body>
<GetBooksByAuthorResult xmlns="http://Oreilly/DotNetEssentials/">
<result>
<xsd:schema id="NewDataSet" . . . >
< . . . content omitted . . . >
</xsd:schema>
<NewDataSet xmlns=">
<Books>
<title_id>BU1032</title_id>
<title>The Busy Executive's Database Guide</title>
< . . . more . . . >
</Books>
<Books>
<title_id>BU2075</title_id>
<title>You Can Combat Computer Stress!</title>
< . . . more . . . >
</Books>
<Author>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<phone>415 986-7020</phone>
<address>309 63rd St. #411</address>
<city>Oakland</city>
<state>CA</state>
<zip>94618</zip>
<contract>True</contract>
</Author>
</NewDataSet>
</result>
</GetBooksByAuthorResult>
</soap:Body>
</soap:Envelope>

Figure 6-8 shows the result of the test form after
invoking the GetBooksByAuthor web method using the SOAP protocol.


Figure 6-8. VB client form after calling GetBooksByAuthor


The XSL stylesheet used for transformation of the resulting XML to
HTML is included here for your reference. Notice that since
GetBooksByAuthor returns two tables in the dataset, author and books,
we can display both the author information and the books that this
author wrote:

<html version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<head><title>A list of books</title></head>
<style>
.hdr { background-color=#ffeedd; font-weight=bold; }
</style>
<body>
<B>List of books written by
<I><xsl:value-of select="//Author/au_fname"/>
<xsl:value-of select="//Author/au_lname"/>
(<xsl:value-of select="//Author/city"/>,
<xsl:value-of select="//Author/state"/>)
</I>
</B>
<table style="border-collapse:collapse" border="1">
<tr>
<td class="hdr">Title</td>
<td class="hdr">Type</td>
<td class="hdr">Price</td>
<td class="hdr">Notes</td>
</tr>
<xsl:for-each select="//Books">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="type"/></td>
<td><xsl:value-of select="price"/></td>
<td><xsl:value-of select="notes"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>

It's ok to go through the previous exercise to
understand how to write a VB application as a front-end client for
web services. However, for your real-life applications, you should
use a SOAP toolkit (such as the Microsoft SOAP toolkit) to make
things easier.

The next couple of examples take a step further. This time, we will
use Perl to access our sample web service PubWS with the help of
ActiveState Perl and SOAP::Lite Perl Library (author: Paul
Kulchenko).[15]

[15] http://www.ActiveState.com/ and http://www.soaplite.com/,
respectively.


With SOAP::Lite Perl Library, all you have to do is to create a
SOAP::Lite object (similar to the proxy object in the C# example) and
setup its uri and proxy properties. Once this is done, you can ask
the proxy object to run the remote web methods. The uri property maps
to the first part of the soapAction and the web method name maps to
the second part. The proxy property maps to the physical location of
the web service itself.

In our first Perl example, we want to call the GetAuthors web method.
This method does not take any parameter and returns a DataSet object.
The SOAP portion of the WSDL states that the soapAction for this
method is
http://Oreilly/DotNetEssentials/GetAuthors. By
default, SOAP::Lite library constructs the soapAction as
<uri string> +
"#" + <web
method name>. This does not agree with web services
written on the .NET Framework, where the soapAction is
uri_string + "/" + web
method name. Fortunately, the SOAP::Lite library provides
a callback-like kind of feature so that we can plug in a sub-routine
to override the default construction of the soapAction string. The
highlighted line of PERL script below basically just concatenates the
uri string and the web method name. For simplicity, we rely on the
fact that the uri string already has the trailing slash.[16]

[16] The trace for the SOAP::Lite library turns out to be extremely
useful because it shows the SOAP request and response as well as
other vital information. Insert trace => all at
the end of the first line of the example.


To run
the program, just type perl
<program name>:

use SOAP::Lite
on_action => sub {sprintf '%s%s', @_};
my $proxy = SOAP::Lite
-> uri('http://Oreilly/DotNetEssentials/')
-> proxy('http://localhost/PubsWS/PubsWS.asmx');
my $method = SOAP::Data->name('GetAuthors')
->attr({xmlns => 'http://Oreilly/DotNetEssentials/'});
my $xmlDataSet = $proxy->call($method);
# my $xmlDataSet = $proxy->GetAuthors( ); # You can also do this
my @authorsNodes = $xmlDataSet->valueof('//Authors');
foreach $author (@authorsNodes) {
print $author->{'au_lname'}, ", ", $author->{'au_fname'}, "\n";
print "\t", $author->{'address'}, "\n";
print "\t", $author->{'city'}, ", ",
$author->{'state'}, " ", $author->{'zip'}, "\n";
print "\t", $author->{'phone'}, "\n";
}

The output of this program is the following:

White, Johnson
10932 Bigge Rd.
Menlo Park, CA 94025
408 496-7223
Green, Marjorie
309 63rd St. #411
Oakland, CA 94618
415 986-7020
. . .

Once the $proxy object is created, we call the GetAuthors web method
knowing that the generated soapAction will be
`http://Oreilly/DotNetEssentials/'
+ `GetAuthors' and the location of
the web service where SOAP::Lite library will try to contact is
http://localhost/PubsWS/PubsWS.asmx. Because
this web method returns a DataSet, which translates to an XML
document, we can parse it with XPATH-like syntax to obtain the list
of authors and traverse the list to display the information for each
author.

Now we want to call the GetBooksByAuthor web method as the second
Perl program. This method takes one string parameter, the SSN for the
author, and returns a DataSet object containing the
author's name and all books he has published. The
SOAP portion of the WSDL document states that the soapAction for this
method is named
http://Oreilly/DotNetEssentials/GetBooksByAuthor,
and the parameter is named sAuthorSSN:

use SOAP::Lite
on_action => sub { sprintf '%s%s', @_ };
my $proxy = SOAP::Lite
-> uri('http://Oreilly/DotNetEssentials/')
-> proxy('http://localhost/PubsWS/PubsWS.asmx');
my $method = SOAP::Data->name('GetBooksByAuthor')
->attr({xmlns => 'http://Oreilly/DotNetEssentials/'});
my @params = (SOAP::Data->name(sAuthorSSN => '998-72-3567'));
my $xmlDataSet = $proxy->call($method => @params);
my $author = $xmlDataSet->valueof('//Author');
print "Books by author:\n";
print $author->{'au_lname'}, ", ", $author->{'au_fname'}, "\n";
print $author->{'address'}, "\n";
print $author->{'city'}, ", ",
$author->{'state'}, " ", $author->{'zip'}, "\n";
print $author->{'phone'}, "\n\n";
my @books = $xmlDataSet->valueof('//Books');
foreach $book (@books) {
print "Type : ", $book->{'type'}, "\n";
print "Title : ", $book->{'title'}, "\n";
print "Price : \$", $book->{'price'}, "\n";
print "Notes : ", $book->{'notes'}, "\n";
print "\n";
}

Type perl yourPerlFile.pl to
run the program. The output is:

Books by author:
Ringer, Albert
67 Seventh Av.
Salt Lake City, UT 84152
801 826-0752
Type : psychology
Title : Is Anger the Enemy?
Price : $10.95
Notes : Carefully researched study of the effects of strong emotions on the
body. Metabolic charts included.
Type : psychology
Title : Life Without Fear
Price : $7
Notes : New exercise, meditation, and nutritional techniques that can reduce
the shock of daily interactions. Popular audience. Sample menus included,
exercise video available separately.

As you can see, you can easily use any type of web service client to
access a .NET web service. Clients of web services need to know how
to communicate in HTTP and understand the WSDL. By the same token,
you can develop a web service in any language and on any platform as
long as it adheres to its WSDL specification.

Now that you have seen how simple it is to create and consume web
services, we should let you in on a small but important
pointthat XML web services is not the solution for everything.
As responsible developers, we have to evaluate the requirements as
well as the applicability of certain technology before applying it.
On one hand, it's great to use web services as a way
to expose legacy functionalities, or to enable integration of
disparate systems on different locations, different platforms, or
different companies. But there is no real reason to make a web
service out of something that can be implemented as a simple
component, or through .NET Remoting, which is discussed in Chapter 4. The decision is up to you. Again, the technology
is just a tool; how you use it is another story.


/ 121