Chapter 6. Web Services

W eb services allow access to software components through standard web protocols such as HTTP and SMTP. Using the Internet and XML, we can now create software components that communicate with others, regardless of language, platform, or culture. Until now, software developers have progressed toward this goal by adopting proprietary componentized software methodologies, such as DCOM; however, because each vendor provides its own interface protocol, integration of different vendors’ components is a nightmare. By substituting the Internet for proprietary transport formats and adopting standard protocols such as SOAP, web services help software developers create building blocks of software, which can be reused and integrated regardless of their location.

In this chapter, we describe the .NET web services architecture and provide examples of a web service provider and several web service consumers.

Web Services in Practice

You may have heard the phrase “software as a service” and wondered about its meaning. The term service, in day-to-day usage, refers to what you get from a service provider. For example, you bring your dirty clothing to a cleaner to use its cleaning service. Software, on the other hand, is commonly thought of as an application, either an off-the-shelf product, or a custom application developed by a software firm. You typically buy the software (or in our case, build the software). It usually resides on some sort of media such as floppy diskette or CD and is sold in a shrink-wrapped package through retail outlets, or, in the case of a web application, the software application is not distributed, but is accessed through a browser.

How can software be viewed as a service? The example we are about to describe might seem far-fetched; however, it is possible with current technology. Imagine the following. As you grow more comfortable with the Internet, you might choose to replace your computer at home with something like an Internet Device, a large-screen PDA designed for use with the Internet. Let’s call the device an iDev. Let’s suppose that with this device, you can be on the Internet immediately through your cell phone, WiFi, or some other means. When you want to do word processing, your iDev is configured to print to a Microsoft Word service somewhere in Redmond, so you can type away without the need to install word-processing software. When you are done, the document can be saved at an iStore server where you can later retrieve it. Notice that for you to do this, the iStore server must host a software service that allows you to store documents. Microsoft might charge you a service fee based on the amount of time your word processor is running and the features you use (such as the grammar and spell checkers). The iStore service charges might vary based on the size of your document and how long it is stored. Of course, none of these charges would come in the mail, but rather through an escrow service where the money would be withdrawn from your bank account or credit card.

As long as your document is in a standard format, such as XML, you’re free to switch word processors at any time. Of course, the document that you store at the iStore server is already in a standard data format. Since iStore utilizes the iMaxSecure software service from a company called iNNSA (Internet Not National Security Agency), the security of your files is assured. And because you use the document storage service at iStore, you also benefit from having your document authenticated and decrypted upon viewing, as well as encrypted at storing time.

While this particular vision of software as a service has yet to be realized, a variety of for-fee and free services have begun to appear. In early 2001, Microsoft announced plans for an integrated collection of consumer-oriented services (known first by their codename, “Hailstorm,” and later as “.NET My Services”) but was forced to abandon the initiative for a variety of reasons, some technical and others legal, political, or market-related. Today, Microsoft offers a variety of user-centric services for identification and authentication, email, instant messaging, automated alerts, calendar, address book, and personal information storage. These are available through its MSN online services, and through Passport (http://www.passport.net), Alerts (http://alerts.microsoft.com), MSN Wallet (http://wallet.msn.com), and Hotmail.

Of greater interest to developers, however, is the availability of these services for use as building blocks in third-party web applications. Hosted by Microsoft and known as Microsoft .NET Services, Passport, Alerts, and MSN Wallet can each be licensed and incorporated into any application that adheres to XML web services standards, regardless of platform. A more recently announced web service is Microsoft MapPoint .NET, which is a set of services that allows you to incorporate maps, driving directions, distance calculations, proximity searches, and other location intelligence into your applications.

In addition to Microsoft, other companies are beginning to offer information and functionality as services over the Web. A recent poster child is Saleforce.com, which offers customer relations management (CRM) software over the Web, either as a standalone product or as a set of services that can be incorporated into third-party applications. The Liberty Alliance is at work defining an authentication service that can be offered as an alternative to Microsoft Passport. And both Google and Amazon now make portions of their business information available through public web service interfaces.

The potential for consumer-oriented and business-to-business web services like Microsoft .NET Services is great, although there are serious and well-founded concerns about security and privacy. In the mean time, web services can be great in interoperability areas where there are needs to expose legacy functionalities or to enable interaction between multiple heterogeneous systems. In one form or another, though, web services are here to stay, so let’s dive in and see what’s underneath.

Web Services Framework

Web services combine the best of both distributed componentization and the World Wide Web, extending distributed computing to broader ranges of client applications. The best thing is that this is done by seamlessly marrying and enhancing existing technologies.

Web Services Architecture

Web services are distributed software components accessible through standard web protocols. The first part of the definition is similar to that for COM/DCOM components. However, it is the second part that distinguishes web services from the crowd. Web services enable software to interoperate with a much broader range of clients. While COM-aware clients can understand only COM components, web services can be consumed by any application that understands how to parse an XML-formatted stream transmitted through HTTP channels. XML is the key technology used in web services and is used in the following areas of the Microsoft .NET web services framework:

Web service wire formats

The technology enabling universal understanding of how to perform data exchanges between the service provider and consumer; the format of data for the request and response.

Web service description in Web Services Description Language (WSDL)

The language describing how the service can be used. Think of this as the instructions on the washing machine at the laundromat telling you where to put quarters, what buttons to push, etc.

Web service discovery

The process of advertising or publishing a piece of software as a service and allowing for the discovery of this service.

Figure 6-1 depicts the architecture of web applications using Windows DNA, while Figure 6-2 shows .NET-enabled web applications architecture. As you can see, communication between components of a web application does not have to be within an intranet. Furthermore, intercomponent communication can also use HTTP/XML.

Windows Distributed interNet Architecture (DNA)
Figure 6-1. Windows Distributed interNet Architecture (DNA)
.NET-enabled web application framework
Figure 6-2. .NET-enabled web application framework

Web Services Wire Formats

You may have heard the phrase “DCOM is COM over the wire.” Web services are similar to DCOM except that the wire is no longer a proprietary communication protocol. With web services, the wire formats rely on more open Internet protocols such as HTTP or SMTP.

A web service is more or less a stateless component running on the web server, exposed to the world through standard Internet protocols. Microsoft .NET web services currently supports three protocols: HTTP GET, HTTP POST, and SOAP over HTTP (Simple Object Access Protocol), explained in the next sections. Because these protocols are standard protocols for the Web, it is very easy for the client applications to use the services provided by the server.

HTTP GET and HTTP POST

As their names imply, both HTTP GET and HTTP POST use HTTP as their underlying protocol. The GET and POST methods of the HTTP protocol have been widely used in ASP (Active Server Pages), CGI, and other server-side architectures for many years now. Both of these methods encode request parameters as name/value pairs in the HTTP request. The GET method creates a query string and appends it to the script’s URL on the server that handles the request. For the POST method, the name/value pairs are passed in the body of the HTTP request message.

SOAP

Similar to HTTP GET and HTTP POST, SOAP serves as a mechanism for passing messages between the clients and servers. In this context, the clients are web services consumers, and the servers are the web services. The clients simply send an XML-formatted request message to the server to get the service over an HTTP channel. The server responds by sending back yet another XML-formatted message. The SOAP specification describes the format of these XML requests and responses. It is simple, yet it is extensible, because it is based on XML.

SOAP is different than HTTP GET and HTTP POST because it uses XML to format its payload. The messages being sent back and forth have a better structure and can convey more complex information compared to simple name/value pairs in HTTP GET/POST protocols. Another difference is that SOAP can be used on top of other transport protocols, such as SMTP in addition to HTTP.[1]

Web Services Description (WSDL)

For web service clients to understand how to interact with a web service, there must be a description of the method calls, or the interface that the web service supports. This web service description document is found in an XML schema called Web Services Description Language (WSDL) . Remember that type libraries and IDL scripts are used to describe a COM component. Both IDL and WSDL files describe an interface’s method calls and the list of in and out parameters for the particular call. The only major difference between the two description languages is that all descriptions in the WSDL file are done in XML.

In theory, any WSDL-capable SOAP client can use the WSDL file to get a description of your web service. It can then use the information contained in that file to understand the interface and invoke your web service’s methods.

WSDL structure

The root of any web service description file is the <definitions> element. Within this element, the following elements provide both the abstract and concrete description of the service:

Types

A container for data type definitions.

Message

An abstract, typed definition of the data being exchanged between the web service providers and consumers. Each web method has two messages: input and output. The input describes the parameters for the web method; the output describes the return data from the web method. Each message contains zero or more <part> parameters. Each parameter associates with a concrete type defined in the <types> container element.

Port type

An abstract set of operations supported by one or more endpoints.

Operation

An abstract description of an action supported by the service. Each operation specifies the input and output messages defined as <message> elements.

Binding

A concrete protocol and data-format specification for a particular port type. Similar to port type, the binding contains operations, as well as the input and output for each operation. The main difference is that with binding, we are now talking about actual transport type and how the input and output are formatted.

Service

A collection of network endpoints—ports. Each of the web service wire formats defined earlier constitutes a port of the service (HTTP GET, HTTP POST, and SOAP ports).

Port

A single endpoint defined by associating a binding and a network address. In other words, it describes the protocol and data-format specification to be used as well as the network address of where the web service clients can bind to for the service.

The following shows a typical WSDL file structure:

<definitions name="" targetNamespace="" xmlns: . . . >

  <types> . . . </types>

  <message name=""> . . . </message>
   . . . 

  <portType name="">
    <operation name="">
      <input message="" />
      <output message="" />
    </operation>
     . . . 
  </portType>
   . . . 

  <binding name="">
    <protocol:binding  . . . >
    <operation name="">
      <protocol:operation  . . . >
      <input> . . . </input>
      <output> . . . </output>
    </operation>
     . . . 
  </binding>
   . . . 

  <service name="">
    <port name="" binding="">
      <protocol:address location="" />
    </port>
     . . . 
  </service>
</definitions>

The <types> element contains physical type descriptions defined in XML Schema (XSD). These types are being referred to from the <message> elements.

For each of the web methods in the web service, there are two messages defined for a particular port: input and output. This means if a web service supports all three protocols: SOAP, HTTP GET, and HTTP POST, there will be six <message> elements defined, one pair for each port. The naming convention used by the Microsoft .NET autogenerated WSDL is:

MethodName + Protocol + {In, Out}

For example, a web method called GetBooks( ) has the following messages:

<message name="GetBooksSoapIn"> . . . </message>
<message name="GetBooksSoapOut"> . . . </message>
<message name="GetBooksHttpGetIn"> . . . </message>
<message name="GetBooksHttpGetOut"> . . . </message>
<message name="GetBooksHttpPostIn"> . . . </message>
<message name="GetBooksHttpPostOut"> . . . </message>

For each protocol that the web service supports, there is one <portType> element defined. Within each <portType> element, all operations are specified as <operation> elements. The naming convention for the port type is:

WebServiceName + Protocol

To continue our example, here are the port types associated with the web service that we build later in this chapter, PubsWS:

<portType name="PubsWSSoap">
  <operation name="GetBooks">
    <input message="GetBooksSoapIn" />
    <output message="GetBooksSoapOut" />
  </operation>
</portType>

<portType name="PubsWSHttpGet">
  <operation name="GetBooks">
    <input message="GetBooksHttpGetIn" />
    <output message="GetBooksHttpGetOut" />
  </operation>
</portType>

<portType name="PubsWSHttpPost">
  <operation name="GetBooks">
    <input message="GetBooksHttpPostIn" />
    <output message="GetBooksHttpPostOut" />
  </operation>
</portType>

We have removed namespaces from the example to make it easier to read.

While the port types are abstract operations for each port, the bindings provide concrete information on what protocol is being used, how the data is being transported, and where the service is located. Again, there is a <binding> element for each protocol supported by the web service:

<binding name="PubsWSSoap" type="s0:PubsWSSoap">
  <soap:binding transport="http://schemas.xmlsoap.org/soap/http" 
                style="document" />
  <operation name="GetBooks">
    <soap:operation soapAction="http://tempuri.org/GetBooks"
                    style="document" />
    <input>
      <soap:body use="literal" />
    </input>
    <output>
      <soap:body use="literal" />
    </output>
  </operation>
</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>

<binding name="PubsWSHttpPost" type="s0:PubsWSHttpPost">
  <http:binding verb="POST" />
  <operation name="GetBooks">
    <http:operation location="/GetBooks" />
    <input>
      <mime:content type="application/x-www-form-urlencoded" />
    </input>
    <output>
      <mime:mimeXml part="Body" />
    </output>
  </operation>
</binding>

For SOAP protocol, the binding is <soap:binding>, and the transport is SOAP messages on top of HTTP protocol. The <soap:operation> element defines the HTTP header soapAction, which points to the web method. Both input and output of the SOAP call are SOAP messages.

For the HTTP GET and HTTP POST protocols, the binding is <http:binding> with the verb being GET and POST, respectively. Because the GET and POST verbs are part of the HTTP protocol, there is no need for the extended HTTP header (like soapAction for SOAP protocol). The only thing we need is the URL that points to the web method; in this case, the <soap:operation> element contains the attribute location, which is set to /GetBooks.

The only real difference between the HTTP GET and POST protocols is the way the parameters are passed to the web server. HTTP GET sends the parameters in the query string, while HTTP POST sends the parameters in the form data. This difference is reflected in the <input> elements of the operation GetBooks for the two HTTP protocols. For the HTTP GET protocol, the input is specified as <http:urlEncoded />, whereas for the HTTP POST protocol, the input is <mime:content type="application/x-www-form-urlencoded" />.

Looking back at the template of the WSDL document, we see that the only thing left to discuss is the <service> element, which defines the ports supported by this web service. For each of the supported protocol, there is one <port> element:

<service name="PubsWS">

  <port name="PubsWSSoap" binding=s0:PubsWSSoap">
    <soap:address
      location="http:// . . . /PubsWs.asmx" />
  </port>

  <port name="PubsWSHttpGet" binding="s0:PubsWSHttpGet">
    <http:address 
      location="http:// . . . /PubsWs.asmx" />
  </port>

  <port name="PubsWSHttpPost" binding="s0:PubsWSHttpPost">
    <http:address 
      location="http:// . . . /PubsWs.asmx" />
  </port>

</service>

Even though the three different ports look similar, their binding attributes associate the address of the service with a binding element defined earlier. Web service clients now have enough information on where to access the service, through which port to access the web service method, and how the communication messages are defined.

Although it is possible to read the WSDL and manually construct the HTTP[2] conversation with the server to get to a particular web service, there are tools that autogenerate client-side proxy source code to do the same thing. We show such a tool in “web services Consumers” later in this chapter.

Web Services Discovery

Even though advertising of a web service is important, it is optional. Web services can be private as well as public. Depending on the business model, some business-to-business (B2B) services would not normally be advertised publicly. Instead, the web service owners would provide specific instructions on accessing and using their service only to the business partner.

To advertise web services publicly, authors post discovery files on the Internet. Potential web services clients can browse to these files for information about how to use the web services—the WSDL. Think of it as the yellow pages for the web service. All it does is point you to where the actual web services reside and to the description of those web services.

The process of looking up a service and checking out the service description is called web service discovery. Currently in .NET, there are two ways of advertising the service: static and dynamic. In both of these, XML conveys the locations of web services.

Static discovery

Static discovery is easier to understand because it is explicit in nature. If you want to advertise your web service, you must explicitly create the .disco discovery file and point it to the WSDL.[3]

All .disco files contain a root element discovery, as shown in the following code sample. Note that discovery is in the namespace http://schemas.xmlsoap.org/disco/, which is referred to as disco in this sample.

<?xml version="1.0" ?>
<disco:discovery xmlns:disco="http://schemas.xmlsoap.org/disco/">
</disco:discovery>

Inside the discovery element, there can be one or more of contractRef or discoveryRef elements. Both of these elements are described in the namespace http://schemas.xmlsoap.org/disco/scl/. The contractRef tag is used to reference an actual web service URL that would return the WSDL or the description of the actual web service contract. The discoveryRef tag, on the other hand, references another discovery document.

This XML document contains a link to one web service and a link to another discovery document:

<?xml version="1.0" ?>
<disco:discovery 
       xmlns:disco="http://schemas.xmlsoap.org/disco/"

       xmlns:scl="http://schemas.xmlsoap.org/disco/scl/">


                  <scl:contractRef ref="http://yourWebServer/yourWebService.asmx?WSDL"/>
                   


                  <scl:discoveryRef ref="http://yourBrotherSite/hisWebServiceDirectory.disco"/>
                  

</disco:discovery>

This sample disco file specifies two different namespaces: disco, which is a nickname for the namespace http://schemas.xmlsoap.org/disco/; and scl, short for http://schemas.xmlsoap.org/disco/scl/. The contractRef element specifies the URL where yourWebService WSDL can be obtained. Right below that is the discoveryRef element, which links to the discovery file on yourBrotherSite web site. This linkage allows for structuring networks of related discovery documents.

Dynamic discovery

As opposed to explicitly specifying the URL for all web services your site supports, you can enable dynamic discovery, which enables all web services underneath a specific URL on your web site to be listed automatically. For your web site, you might want to group related web services under many different directories and then provide a single dynamic discovery file in each of the directories. The root tag of the dynamic discovery file is dynamicDiscovery instead of discovery:

<?xml version="1.0" encoding="utf-8"?>
<dynamicDiscovery xmlns="urn://schemas-dynamic:disco.2000-03-17" />

You can optionally specify exclude paths so that the dynamic mechanism does not have to look for web services in all subdirectories underneath the location of the dynamic discovery file. Exclude paths are in the following form:

<exclude path="pathname" />

If you run IIS as your web server, you’d probably have something like the following for a dynamic discovery file:[4]

<?xml version="1.0" encoding="utf-8"?>
<dynamicDiscovery xmlns="urn://schemas-dynamic:disco.2000-03-17">
    <exclude path="_vti_cnf" />
    <exclude path="_vti_pvt" />
    <exclude path="_vti_log" />
    <exclude path="_vti_script" />
    <exclude path="_vti_txt" />
    <exclude path="Web References" />
</dynamicDiscovery>

Discovery setting in practice

A combination of dynamic and static discovery makes a very flexible configuration. For example, you can provide static discovery documents at each of the directories that contain web services. At the root of the web server, provide a dynamic discovery document with links to all static discovery documents in all subdirectories. To exclude web services from public viewing, provide the exclude argument to XML nodes to exclude their directories from the dynamic discovery document.

UDDI

Universal Description, Discovery, and Integration (UDDI) Business Registry is like a yellow pages of web services. It allows businesses to publish their services and locate web services published by partner organizations so that they can conduct transactions quickly, easily, and dynamically with their trading partner.

Through UDDI APIs, businesses can find services over the web that match their criteria (e.g., cheapest fare), that offer the service they request (e.g., delivery on Sunday), and so on. Currently backed by software giants such as Microsoft, IBM, and Ariba, UDDI is important to web services because it enables access to businesses from a single place.[5]

The System.Web.Services Namespace

Now that we have run through the basic framework of Microsoft .NET web services, let us take a look inside what the .NET SDK provides us in the System.Web.Services namespace.

There are only a handful of classes in the System.Web.Services namespace and the most important ones for general use are:

WebService

The base class for all web services.

WebServiceAttribute

An attribute that can be associated with a web service-derived class.

WebMethodAttribute

An attribute that can be associated with public methods within a web service-derived class.

The two essential classes for creating web services are the WebService base class and WebMethodAttribute. We make use of these classes in the next section, where we implement a web service provider and several web service consumers. WebService is the base class from which all web services inherit. It provides properties inherent to legacy ASP programming such as Application, Server, Session, and a new property, Context, which now includes Request and Response.

The WebMethodAttribute class allows you to apply attributes to each public method of your web service. Using this class, you can assign specific values to the following attributes: description, session state enabling flag, message name, transaction mode, and caching. See the following section for an example of attribute setting in C# and VB.

The WebServiceAttribute class is used to provide more attributes about the web service itself. You can display a description of the web service, as well as the namespace to which this web service belongs.

Web Services Provider

In this section, we describe how to develop a web service, first from the point of view of service providers and then of the consumers. Web services providers implement web services and advertise them so that the clients can discover and make use of the services. Because web services run on top of HTTP, there must be a web server application of some sort on the machine that hosts the web services. This web server application can be Microsoft Internet Information Services (IIS), Apache, or any other program that can understand and process the HTTP protocol. In our examples, we use Microsoft IIS, since that is the only web server currently supported by .NET.

Web Service Provider Example

We will be building a web service called PubsWS to let consumers get information from the sample Pubs database. All data access will be done through ADO.NET, so read Chapter 5 before attempting the examples.

Creating a web service is a three-step process:

  1. Create a new asmx file for the web service. This must contain the <% webservice . . . %> directive, as well as the class that provides the web service implementation. To the web service clients, this asmx file is the entry point to your web service. You need to put this in a virtual directory that has the executescripts permission turned on.

  2. Inherit from the WebService class of the System.Web.Services namespace. This allows the derived class to access all the normal ASP.NET objects exposed in the WebService base class such as Application, Session, Server, Request, and Response.[6] It is highly recommended that you specify a namespace for your web service before publishing it publicly because the default namespace, http://tempuri.org/, will not uniquely identify your web service from other web services. To do this, tag the WebService class with the Namespace attribute, specifying your own namespace.

  3. Tag the public methods with WebMethod attributes to make web methods— public methods of a distributed component that are accessible via the Web. You don’t have to tag a method as WebMethod unless you want that method to be published as a web method.

The following C# code demonstrates a simple web service[7] that exposes four methods to Internet clients.

We emphasize “Internet” because anyone that can access this asmx file on the web server can access these methods, as opposed to your COM component, which can be accessed only by COM clients:

               <%@ WebService Language="C#" Class="PubsWS.PubsWS" %>


               namespace PubsWS
               {
               using System;
               using System.Data;
               using System.Data.OleDb;
               using System.Web;
               using System.Web.Services;

               [WebService(Namespace="http://Oreilly/DotNetEssentials/")]
               public class PubsWS : WebService
               {
               private static string m_sConnStr =
"provider=sqloledb;server=(local);database=pubs; Integrated Security=SSPI";


               [WebMethod(Description="Returns a DataSet containing all authors.")]

    public DataSet GetAuthors(  )
    {
      OleDbDataAdapter oDBAdapter;
      DataSet oDS;

      oDBAdapter = new OleDbDataAdapter("select * from authors", 
                                        m_sConnStr);
      oDS = new DataSet(  );
      oDBAdapter.Fill(oDS, "Authors");
      return oDS;
    }


    [WebMethod]

    public DataSet GetAuthor(string sSSN)
    {
      OleDbDataAdapter oDBAdapter;
      DataSet oDS;

      oDBAdapter = new OleDbDataAdapter(
                   "select * from authors where au_id ='"
                   + sSSN + "'", m_sConnStr);
      oDS = new DataSet(  );
      oDBAdapter.Fill(oDS, "SelectedAuthor");
      return oDS;
    }


    [WebMethod(MessageName="GetBooksByAuthor",


               Description="Find books by author's SSN.")]

    public DataSet GetBooks(string sAuthorSSN) 
    {
      OleDbDataAdapter oDBAdapter;
      DataSet oDS;

      oDBAdapter = new OleDbDataAdapter(
                      "select * from titles inner join titleauthor on " +
                      "titles.title_id=titleauthor.title_id " +
                      "where au_id='" + sAuthorSSN + "'", m_sConnStr);
      oDS = new DataSet(  );
      oDBAdapter.Fill(oDS, "Books");
      oDBAdapter = new OleDbDataAdapter("select * from authors " +
                      "where au_id='" + sAuthorSSN + "'", m_sConnStr);
      oDBAdapter.Fill(oDS, "Author");

      return oDS;
    }


    [WebMethod]

    public DataSet GetBooks(  ) 
    {
      OleDbDataAdapter oDBAdapter;
      DataSet oDS;

      oDBAdapter = new OleDbDataAdapter("select * from titles" ,
                                        m_sConnStr);
      oDS = new DataSet(  );
      oDBAdapter.Fill(oDS, "Books");
      return oDS;
    }

  } // End PubsWS
}

If you are familiar with ASP, you may recognize the usage of the @ symbol in front of keyword WebService. This WebService directive specifies the language of the web service so that ASP.NET can compile the web service with the correct compiler. This directive also specifies the class that implements the web service so it can load the correct class and employ reflection to generate the WSDL for the web service.

Because PubsWS also uses ADO.NET’s OLE DB provider for its data-access needs, we have to add a reference to System.Data and System.Data.OleDb, in addition to the System, System.Web, and System.Web.Services namespaces.

Class PubsWS inherits from WebService with the colon syntax that should be familiar to C++ or C# developers:

public class PubsWS : WebService

The four methods that are tagged with WebMethod attributes are GetAuthors( ), GetAuthor( ), GetBooks(string), and GetBooks( ). In C#, you can tag public methods with a WebMethod attribute using the [] syntax. In VB, you must use <>. For example, in VB, the second method would be declared as:

<WebMethod(  )> Public Function GetAuthor(sSSN As String) As DataSet

By adding [WebMethod] in front of your public method, you make the public method callable from any Internet client. What goes on behind the scenes is that your public method is associated with an attribute, which is implemented as a WebMethodAttribute class. WebMethodAttribute has six properties:

BufferResponse (boolean)

Controls whether or not to buffer the method’s response.

CacheDuration

Specifies the length of time in seconds to keep the method response in cache; the default is not to hold the method response in cache (0 seconds). A cache hit is when an identical call with identical parameters is requested. The cached response is used to avoid re-processing.

Description

Provides additional information about a particular web method.

EnableSession (boolean)

Enables or disables session state. If you don’t want to use session state for the web method, you should make sure that this flag is disabled so the web server doesn’t have to generate and manage session IDs for each user accessing the method.

MessageName

Distinguishes web methods with the same names. For example, if you have two different methods called GetBooks (one method retrieves all books while the other method retrieves only books written by a certain author) and you want to publish both of these methods as web methods, the system will have a problem trying to distinguish the two methods since their names are duplicated. You have to use the MessageName property to make sure all service signatures within the WSDL are unique. If the protocol is SOAP, MessageName is mapped to the SOAPAction request header and nested within the soap:Body element. For HTTP GET and HTTP POST, it is the PathInfo portion of the URI (as in http://localhost//PubsWS/PubsWS.asmx/GetBooksByAuthor).

TransactionOption

Can be one of five modes: Disabled, NotSupported, Supported, Required, and RequiresNew. Even though there are five modes, web methods can only participate as the root object in a transaction. This means both Required and RequiresNew result in a new transaction being created for the web method. The Disabled, NotSupported, and Supported settings result in no transaction being used for the web method. The TransactionOption property of a web method is set to Disabled by default.

To set up these properties, pass the property name and its value as a name = value pair:

 [WebMethod(Description="Returns a DataSet containing all authors.")]
 public DataSet GetAuthors(  )

You can separate multiple properties with a comma:

 [WebMethod(MessageName="GetBooksByAuthor",
            Description="Find books by author's SSN.")]
 public DataSet GetBooks(string sAuthorSSN)

Web.Config

If you set up your web services from scratch, you should also need to provide the configuration file (web.config) in the same directory as your asmx file. This configuration file allows you to control various application settings about the virtual directory. Here, we set the authentication mode to None to make our web services development and testing a little easier. When you release your web services to the public, you should change this setting to Windows, Forms, or Passport instead of None:

<configuration>
  <system.web>
    <authentication mode="None" />
  </system.web>
</configuration>

The following list shows the different modes of authentication:

Forms

Basic Forms authentication is where unauthenticated requests are redirected to a login form.

Windows

Authentication is performed by IIS in one of three ways: basic, digest, or Integrated Windows Authentication.

Passport

Unauthenticated requests to the resource are redirected to Microsoft’s centralized authentication service. When authenticated, a token is passed back and used by subsequent requests.

In v1.1 of .NET Framework, for security reasons, HttpGet and HttpPost protocol are disabled by default. To override the default and enable or disable any particular protocols, the web.config can be modified as the following:

<configuration>
 . . . 
  <webServices>
    <protocols>
      <add name="HttpGet" />
      <add name="HttpPost" />
    </protocols>
  </webServices>
 . . . 
</configuration>

Discover files

After creating the web service, you can provide the supporting files to help in the discovery of the service. The static discovery disco file is:[8]

<?xml version="1.0" ?>
<disco:discovery xmlns:disco="http://schemas.xmlsoap.org/disco/" 
                 xmlns:scl="http://schemas.xmlsoap.org/disco/scl/">
<scl:contractRef ref="http://localhost/PubsWS/PubsWS.asmx?WSDL"/> 
</disco:discovery>

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.

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.

An autogenerated web services consumer
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:[11]

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.

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.

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.

C# web service client after calling GetAuthors( )
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]

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.

VB client form to test web services
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 & "	emplateAuthor.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.

VB client form after calling GetBooks
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.

VB client form after calling GetAuthor
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 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 & "	emplateAuthorTitle.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.

VB client form after calling GetBooksByAuthor
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]

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]

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'}, "
";
  print "	", $author->{'address'}, "
";
  print "	", $author->{'city'}, ", ", 
              $author->{'state'}, " ", $author->{'zip'}, "
";
  print "	", $author->{'phone'}, "
";
}

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:
";
print $author->{'au_lname'}, ", ", $author->{'au_fname'}, "
";
print $author->{'address'}, "
";
print $author->{'city'}, ", ", 
      $author->{'state'}, " ", $author->{'zip'}, "
";
print $author->{'phone'}, "

";

my @books = $xmlDataSet->valueof('//Books'),
foreach $book (@books) {
  print "Type  : ", $book->{'type'}, "
";
  print "Title : ", $book->{'title'}, "
";
  print "Price : $", $book->{'price'}, "
";
  print "Notes : ", $book->{'notes'}, "
";
  print "
";
}

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 point—that 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.

Async Web Services

All of the web service client examples we’ve used so far make a call to the web service and wait for data before continuing work. While this approach might be appropriate for small, quick calls, such synchronous calls might yield an unacceptable response time. The web service has to perform a time-consuming task. Asynchronous web service calls are the answer in this case. This section explores how the .NET Framework web services enable clients to call web methods asynchronously, yielding a better overall response time for an application.

If you look back at the generated source for the web service proxy in the previous section, you will find that for every web method there are three separate proxy methods generated. The following shows the three proxy methods: GetAuthors( ), BeginGetAuthors( ), and EndGetAuthors( ):

public System.Data.DataSet GetAuthors(  ) {
 object[] results = this.Invoke("GetAuthors", new object[0]);
 return ((System.Data.DataSet)(results[0]));
}
    
public System.IAsyncResult BeginGetAuthors(System.AsyncCallback callback, 
        object asyncState) {
 return this.BeginInvoke("GetAuthors",new object[0],callback,asyncState);
}
    
public System.Data.DataSet EndGetAuthors(System.IAsyncResult asyncResult) {
 object[] results = this.EndInvoke(asyncResult);
 return ((System.Data.DataSet)(results[0]));
}

In the earlier web service consumer example, we’ve used the proxy method GetAuthors( ) to call the web method on the other side of the HTTP channel. The following example shows how the other two methods can be used to perform an asynchronous web method call.

As it turns out, there are a couple of different ways to use the Begin MethodName ( ) and End MethodName ( ) to call the web method asynchronously. We will show you each one and describe how they are different from one another.

The first way will be calling Begin MethodName ( ) passing in null for both the callback and the asyncState objects. We will poll on the IAsyncResult object until it is completed while simulating other works. When the call is done, we call End MethodName ( ) to complete the web method call and obtain the DataSet result:[17]

WSPubsWS.PubsWS oProxy = new WSPubsWS.PubsWS(  );
IAsyncResult result;
// Async call polling (cannot run this in debugger)
Console.Write("Async Polling");
result = oProxy.BeginGetAuthors(null, null);
while(!result.IsCompleted) {
  Console.Write(" . . . "); Thread.Sleep(5);
}
DataSet oDSAsync = oProxy.EndGetAuthors(result);
oDSAsync.WriteXml("outputpolling.xml");
Console.WriteLine("done");

Although this might not be the best way, it demonstrates one way of performing an asynchronous call to a web method.

The second way still does not use the callback mechanism. Be patient. Again, we use the IAsyncResult object to perform a block waiting on the remote call. Block waiting put the current thread to sleep, giving up the CPU to other processes or threads until the wait handle is signaled. Depending on your application, this might be how you would want to do it:

// Async call with waithandle
Console.WriteLine("Async, processing then wait for handle");
result = oProxy.BeginGetAuthors(null, null);
Console.WriteLine(" . . . Processing some more . . . ");
result.AsyncWaitHandle.WaitOne(  );        // block on handle
DataSet oDSAsyncWait = oProxy.EndGetAuthors(result);
oDSAsyncWait.WriteXml("outputWaiting.xml");
Console.WriteLine("done");

The third and fourth ways use the callback mechanism so we will know exactly when to complete the web method call with the End MethodName ( ). We make the call to the Begin MethodName ( ) passing in the callback delegate. When the web method call completes, we will get called back. Since this example is done as a console application, a little more setting up is required; otherwise, the application might exit before the callback occurs. This is done through the AutoResetEvent object (a synchronization primitive similar to a mutex). Basically, we want to set up a wait object, call the async web method, and wait for the signal from the callback before continuing with the application:

// Async call with callback 1
AutoResetEvent oWait = new AutoResetEvent(false);
Console.WriteLine("Async, processing then wait for callback");
CallBack cb = new CallBack(oProxy, oWait);
Result =oProxy.BeginGetAuthors(new AsyncCallback(cb.CallBackMethod), null);
Console.WriteLine(" . . . Processing some more . . . ");
Console.WriteLine("Application waits for callback rendezvous");
oWait.WaitOne(  );
Console.WriteLine("done");

The CallBack class needs to have the proxy to complete the web method call. It also needs the AutoResetEvent to signal the main application flow to continue. The following is the definition of the CallBack class at this moment:

public class CallBack {
  public CallBack(WSPubsWS.PubsWS oProxy, AutoResetEvent oWait) {
    m_oProxy = oProxy;
    m_oWait = oWait;
  }
  public void CallBackMethod(IAsyncResult result) {
    DataSet oDSAsyncCB;
    oDSAsyncCB = m_oProxy.EndGetAuthors(result);
    oDSAsyncCB.WriteXml("outputCallback.xml");
    m_oWait.Set(  );
  }
  private WSPubsWS.PubsWS m_oProxy;
  private AutoResetEvent m_oWait;
}

In the next example, we pass both the callback and the asyncState object to the Begin MethodName ( ) method. We already know all about the callback object. What about this asyncState thing? It actually can be anything you want to pass to the callback object. We will pass the proxy object itself via this asyncState object so we will know how to complete the web method call when the callback is invoked. The following code segment shows how this is done:

// async call with callback 2
oWait.Reset(  );
Console.WriteLine("Async, processing then wait for callback also");
CallBack cbWithData = new CallBack(oWait);
result=oProxy.BeginGetAuthors(new AsyncCallback(cbWithData.CallBackMethod), oProxy);
Console.WriteLine("Processing some more data");
Console.WriteLine("Application waits for callback rendezvous");
oWait.WaitOne(  );
Console.WriteLine("done");

This time, the CallBack object is instantiated with only one parameter. Changes to the CallBack class are highlighted in the following code block. When the callback method is invoked, you can cast the IAsyncResult object’s AsyncState back to whatever type you passed into the second parameter of the Begin MethodName ( ) method. In this case, we cast the AsyncState object back to the proxy in order to complete the web method call with End MethodName ( ) to obtain the resulting DataSet:

public class CallBack {
  public CallBack(WSPubsWS.PubsWS oProxy, AutoResetEvent oWait) {
    m_oProxy = oProxy;
    m_oWait = oWait;
  }
  public CallBack(AutoResetEvent oWait) {
                m_oWait = oWait;
              }
  public void CallBackMethod(IAsyncResult result) {
    DataSet oDSAsyncCB;
    if(m_oProxy == null) {
                  WSPubsWS.PubsWS oTmp = (WSPubsWS.PubsWS)result.AsyncState;
                  oDSAsyncCB = oTmp.EndGetAuthors(result);
                  oDSAsyncCB.WriteXml("outputCallbackWithData.xml");
                } else {
      oDSAsyncCB = m_oProxy.EndGetAuthors(result);
      oDSAsyncCB.WriteXml("outputCallback.xml");
    }
                m_oWait.Set(  );
  }
  private WSPubsWS.PubsWS m_oProxy;
  private AutoResetEvent m_oWait;
}

In a Windows Form application, you can start the web service call via a button clicked or a menu clicked and still can have other parts of the application be responsive to the user. We will leave that example to you as an exercise.

SOAP Header in Web Services

As you have seen from the web service consumer using SOAP, the data being passed back and forth as is structured XML inside the body of the HTTP package. In particular it has the following format:

<Envelope>
  <Header>
    < . . . >
  </Header>
  <Body>
    < . . . >
  </Body>
</Envelope>

We have seen how to pass data from the client to the web service through the Body of the Envelope. In this section, we show how to use the Header of the Envelope.

Through this optional header node, the developers can pass information that does not relate to any particular web method or information that is common to all web methods. Of course, you can pass information that does not related to any particular web method in a method call itself, such as InitiateService(param) enforcing a usage rule that this method has to be called first. This is not an elegant solution. Just add the param of the InitiateService to the header. On the other hand, if all web methods of your service require a common parameter, wouldn’t it be nice if you could just set up the header and didn’t have to worry about this parameter for each of the web methods. Examples of header information are a security token, a payment token, priority, and so on; there is no reason to pass these pieces of common information via every web method.

Once you construct your web service to use a SOAP header, the WSDL will instruct the client that a header node is in place so that a web service client knows how to set up the header information before making a call to the web service methods. The following example should provide some clarification:

<%@ WebService Language="C#" Class="TestHeader.PayWS" %>

namespace TestHeader {
  using System;
  using System.Web;
  using System.Web.Services;
  using System.Web.Services.Protocols;

  public class Payment : SoapHeader {
    public string CreditCardNumber;
  }

  [WebService(Namespace="http://Oreilly/DotNetEssentials/")]
  public class PayWS : WebService {

  public Payment clientPayment;

  [WebMethod, SoapHeader("clientPayment")]
  public string Task1(  ) {
    return string.Format("Task1 performed.  " +
          "Charging $25,000.00 on credit card: {0}", 
      clientPayment.CreditCardNumber);
    }

  [WebMethod, SoapHeader("clientPayment")]
  public string Task2(  ) {
    return string.Format("Task2 performed.  " +
          "Charging $4,500.00 on credit card: {0}", 
      clientPayment.CreditCardNumber);
    }
  }
}

In this example, we create a web service with two methods, Task1 and Task2. Both of these methods use the Payment object in the soap header.

The SOAP request and response format for Task1 follows:

POST /SOAPHeader/PayWS.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://Oreilly/DotNetEssentials/Task1"

<?xml version="1.0" encoding="utf-8"?>
<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:Header>
                <Payment xmlns="http://Oreilly/DotNetEssentials/">
                  <CreditCardNumber>string</CreditCardNumber>
                </Payment>
              </soap:Header>
  <soap:Body>
    <Task1 xmlns="http://Oreilly/DotNetEssentials/" />
  </soap:Body>
</soap:Envelope>

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length

<?xml version="1.0" encoding="utf-8"?>
<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>
    <Task1Response xmlns="http://Oreilly/DotNetEssentials/">
      <Task1Result>string</Task1Result>
    </Task1Response>
  </soap:Body>
</soap:Envelope>

This is different than the soap envelope we’ve seen for PubsWS where there was no SOAP header. In this case, the header is an object of type Payment and this Payment class has a string property called CreditCardNumber.

Our example expects the clients to this web service to pass in the Payment object. The following is the client code. Of course, you will have to generate the proxy class for the PayWS web service using wsdl.exe and compile this proxy along with the client code:

public class ClientWS {
  public static void Main(  ) {
    // Create a proxy
    PayWS oProxy = new PayWS(  );

    // Create the payment header.
    Payment pmt = new Payment(  );
    pmt.CreditCardNumber = "1234567890123456";

    // Attach the payment header to the proxy.
    oProxy.PaymentValue = pmt;

    // Call Task1.
    System.Console.WriteLine(oProxy.Task1(  ));

    // Call Task2.
    System.Console.WriteLine(oProxy.Task2(  ));
  }
}

The output of the client:

Task1 performed.  Charging $25,000.00 on credit card: 1234567890123456
Task2 performed.  Charging $4,500.00 on credit card: 1234567890123456

The above is a trivial usage of SOAP header. In reality, the SOAP headers are probably not as simple as our Payment class.

Microsoft, IBM, and a number of other companies have been working on the Global XML web services Architecture (GXA) that defines a framework on how to build standardized web services incorporating security, reliability, referral, routing, transaction, and so on. A number of GXA specifications use SOAP Header as the means for this infrastructure.

Web Services and Security

This section demonstrates how to incorporate security into your web service. We will do so in two ways: system security and application security. System-level security allows for restricting access to the web services from unauthorized clients. It is done in a declarative fashion, whereas application-level security is more flexible. With system-level security, you will most likely have the list of authorized clients’ IP addresses that you will let access your web service through the use of some configuration-management tools. With application-level security, you will incorporate the authentication into your web service, thus providing a more flexible configuration.[18]

System Security

Because web services communication is done through HTTP, you can apply system-level security on web services just as you do for other web pages or resources on your web site.

There are a number of different ways you can secure your web services. For a business-to-business (B2B) solution, you can use the IIS Administration Tool to restrict or grant permission to a set of IP addresses, using the Internet Protocol Security (IPSec) to make sure that the IP address in the TCP/IP header is authenticated. When you rely only on the client to provide the IP in the TCP/IP header, hackers can still impersonate other host IPs when accessing your web services. IPSec authenticates the host addresses using the Kerberos authentication protocol. You can also use a firewall to restrict access to your web services for your partner companies. For a business-to-consumer (B2C) scenario, you can take advantage of the authentication features of the HTTP protocol.

To show you how to use the authentication feature of the HTTP protocol to secure your web services, let’s revisit the example web service we have in this chapter, PubsWS. All we have to do to secure PubsWS web service is go to the IIS Admin Tool and choose to edit the File Security properties for the PubsWS.asmx. Instead of keeping the default setting, which leaves this file accessible to all anonymous users, we change this setting to “Basic Authentication” only, which means unchecking “Anonymous Access” and checking only “Basic Authentication” in the Authenticated Access frame. After this change, only users that pass the authentication can make use of the web service.

For real-life situations, of course, we are not going to use just the Basic Authentication method, because it sends the username and password in clear text through the HTTP channel. We would choose other methods, such as Secure Sockets Layer (SSL) underneath Basic Authentication, so that the data passed back and forth is secure. Available methods include:

Basic Authentication

Sends the username and password to the Web Server in clear text. IIS authenticates the login against the database of users for the domain.

Basic over SSL Authentication

Similar to Basic Authentication, except that the username and password are sent with SSL encryption.

Digest Authentication

Uses a hashing technique, as opposed to SSL encryption, to send client credentials securely to the server.

Integrated Windows Authentication

Good for intranet scenarios only. Uses the login information of the client for authentication.

Client Certificates Authentication

Requires each of the clients to obtain a certificate that is mapped to a user account. The use of client-side digital certificates is not widespread at this time.

Application Security

A less systematic way of securing your web services involves taking security into your own hands. You can program your web services so that all of their methods require an access token, which can be obtained from the web service after sending in the client’s username and password. The client credentials can be sent to the server through SSL, which eliminates the risk of sending clear-text passwords across the wire. Through this SSL channel, the server returns an access token to the caller, who can use it to invoke all other web service methods. Of course, all of the other web methods that you publish must have one parameter as the token. A simple pseudocode example of a bank account web service can be the following:

web service Bank Account
  Web Methods:
    Login(user id, password) returns access token or nothing
    Deposit(access token, account number, amount, balance) returns boolean
    Withdraw(access token, account number, amount, balance) returns boolean

The only method that should be on SSL is the Login method. Once the token is obtained, it can be used for other web methods. Of course, you should be able to make sure that subsequent calls using this token are coming from the same IP as the Login( ) call. You can also incorporate an expiration timestamp on this access token to ensure that the token only exists in a certain time frame until a renewal of the access token is needed.

You can also use public/private keys (asymmetric) encryption for better key- management. The following scenario might suit your needs.

The client gets the server’s public key and uses it to encrypt the requesting data (possibly including the client’s private symmetric encryption key) before calling the web service. This ensures that the requesting data is encrypted and only the server can decrypt it. The server decrypts the data using the private key, figures out what the request is and uses the client’s private key to encrypt the response before sending it back to the client. This time, the response data is encrypted. The client then decrypts the response package with its private symmetric key to view clear data. Since asymmetric cryptography operations are always much slower than symmetric, it’s probably best if you only use asymmetric cryptography for key distribution. Once the key is communicated, you can use the symmetric operation for better performance. Rolling your own security is always harder than using a standard security solution. There are numerous areas you will have to manage yourself such as identity authentication, message authentication, and so on. Be forewarned if you choose this route.

The Microsoft .NET Cryptographic Services can be very useful if you choose to apply application security for your web services. DES, RC2, RC4, TripleDES, and RSA cryptography algorithms are supported along with hashing methods such as SHA and MD5. These implementations in the .NET library enable developers to avoid low-level grunt work and focus on the application logic.

Summary

In this chapter, we’ve introduced you to the new paradigm of applications—the enterprise application. You are no longer restricted to homogeneous platforms for implementing your solutions. With Microsoft web services, your solutions can span many different platforms because the communication between web services is done through standard Internet protocols such as HTTP and XML. The distributed components in Windows DNA with which you may be familiar are now replaced by web services. Using web services as components in a distributed environment allows for a heterogeneous system. The web services in your system can not only be implemented in different languages, but can even be on different platforms. Because of this greater interoperability, web services are eminently suitable for B2B integration.



[1] SOAP is recognized as the cross-platform standard protocol to use for web services both inside and outside the Microsoft circle.

[2] Current Microsoft .NET SOAP implementation runs on top of HTTP.

[3] If you use Visual Studio .NET to create your web service, the discovery file is created automatically.

[4] VS.NET uses vsdisco as the extension for its dynamic discovery files.

[5] UDDI SDK can be downloaded from Microsoft as a .NET assembly.

[6] Access to the Request and Response objects can be done through the Context property of the WebService class.

[7] For security reasons, the current release of ASP.NET runs as the account ASPNET. If you are using integrated security to access database resources, you must grant database access to the ASPNET account. You can also enable impersonation in the web.config or machine.config file:

<system.web>
 <identity impersonate="true" userName="." password=""/>
</system.web>

If you set impersonate to true but leave UserName and password blank, the application will run as MachineNameIUSR_MachineName, so make sure to grant this user (or whatever userName you specify) database access.

[8] This code snippet assumes the virtual directory you set up is /PubsWS on your local web server.

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

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

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

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

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

[14] You can find the proxy source file under Web ReferencesReferenceName 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.)

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

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

[17] The proxy in this example was generated with a namespace “WSPubsWS” instead of the default global namespace.

[18] As always, consult your security expert on this decision.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.147.215