ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


.NET Framework Essentials, 2nd Ed.: Web Services, Part 3

by Thuan L. Thai, Hoang Lam
03/11/2002

Editor's Note: This is the third of four excerpts from the second edition of O'Reilly's .NET Framework Essentials, 2nd Ed. by Thuan L. Thai and Hoang Lam. The complete series of excerpts covers Web services in the context of the .NET framework. Part one was an overview of .NET Web services. Part two focused on Web service providers. This third part covers Web services consumers.

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 both VB6 and .NET languages, such as C# and VB.NET, to demonstrate the cross-language/cross-platform benefits of Web Services. For example, you can replace the example in VB6 with Perl running on Unix, and the Web Services should still be serving.

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. 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.

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>

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:

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 (Web Services Description Language) 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.

Related Reading

.NET Framework Essentials
By Thuan L. Thai, Hoang Lam

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:SOAP parameters, the PubsWS derives from either the HttpGetClientProtocol or SoapHttpClientProtocol class.

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. 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.

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 GetBooks(  ) 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 GetBooks(  ) 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 the proxy 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.

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 datatype 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 datatype 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 datatype 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 datatype 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 datatype is used by the HTTP GET call -->
      <s:element name="GetBooks">
        <s:complexType />
      </s:element>
     <!-- This datatype 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 datatype 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 method--in 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>
<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 file--specifically, the SOAP section--we 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

Previously in the Series

.NET Framework Essentials, 2nd Ed.: Web Services, Part 1


.NET Framework Essentials, 2nd Ed.: Web Services, Part 2

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>

As you can see, we can easily have any type of Web Service clients accessing .NET Web Services. The clients to the Web Services need to know how to communicate only in HTTP and understand the Web Services Description Language (WSDL) to communicate with the server. By the same token, we can also develop Web Services in any language and on any platform as long as we adhere to the specification of WSDL.

The next and final installment in this series covers Web Services and Security.

Copyright © 2009 O'Reilly Media, Inc.