perfectxml.com
 Basic Search  Advanced Search   
Topics Resources Free Library Software XML News About Us
  You are here: home Free Library O'Reilly Books » .NET Framework Essentials, 2nd Edition Friday, 7 September 2007
 
.NET Framework Essentials, 2nd Edition

ISBN: 0596003021
Author(s): Thuan L. Thai, Hoang Lam
Second Edition February 2002

.NET Framework Essentials, 2nd Edition is a concise and technical overview of the Microsoft .NET Framework. Covered here are all of the most important topics--from the underlying Common Language Runtime (CLR) to its specialized packages for ASP.NET, Web Forms, Windows Forms, XML and data access (ADO.NET). The authors survey each of the major .NET languages, including Visual Basic .NET, C# and Managed C++.

Buy this book!

Copyright O'Reilly & Associates, Inc.. Used with permission.

Chapter 6

Web Services

Web 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 services" 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 the cleaner to use its cleaning service. Software, on the other hand, is commonly known as an application, either off-the-shelf, 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.

How can software be viewed as services? 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 attached to the Internet, you might choose to replace your computer at home with something like an Internet Device, specially designed for use with the Internet. Let's call it an iDev. With this device, you can be on the Internet immediately. If you want to do word processing, you can point your iDev to a Microsoft Word service somewhere in Redmond and 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 to allow you to store documents. Microsoft would charge you a service fee based on the amount of time your word processor is running and which features you use (such as the grammar and spell checkers). The iStore service charges vary based on the size of your document and how long it is stored. Of course, all these charges won't come in the mail, but rather through an escrow service where the money can be piped from and to your bank account or credit card.

This type of service aims to avoid the need to upgrade your Microsoft Word application. If you get tired of Microsoft Word, you can choose to use a different software service from another company. 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.

All of these things can be done today with Web Services.

In fact, Microsoft has launched a version of the "software as service" paradigm with its Passport authentication service. Basically, it is a centralized authentication service that you can incorporate into your web sites. At sites using the Passport authentication service, it's no longer necessary to memorize or track numerous username/password pairs.

Recently, Microsoft also announced .NET My Services (formerly codenamed "HailStorm"), a set of user-centric Web Services, including identification and authentication, email, instant messaging, automated alert, calendar, address book, and storage. As you can see, most of these are well-known services that are provided separately today. Identification and authentication is the goal of the Passport project. Email might map to Hotmail or any other web-based email services. Instant messaging and automated alert should be familiar to you if you use MSN Messenger Service or AOL Instant Messenger. A calendar and address book are usually bundled together with the web-based email service. Consolidating these user-centric services and exposing them as Web Services would allow the user to publish and manage his own information.

A .NET My Services customer can also control access permission to the data to allow or restrict access to content. These services also allow other users, organizations, and smart devices to communicate and retrieve information about us. For example, how many times have you been on the road with your mobile phone and wanted your contact list from Outlook? Your mobile phone should be able to communicate with your address book Web Service to get someone's phone number, right? Or better yet, if your car broke down in the middle of nowhere, you should be able to use your mobile phone to locate the nearest mechanic. The user is in control of what information is published and to whom the information will be displayed. You would probably have it set up so that only you can access your address book, while the yellow pages Web Service that publishes the nearest mechanic shop to your stranded location would be publicly accessible to all.

Currently, users store important data and personal information in many different places. With .NET My Services, information will be centrally managed. For example, your mechanic might notify you when it's time for your next major service. Or when you move and change your address, instead of looking up the list of contacts you wish to send the update to, .NET My Services will help you publish your update in one action.

The potential for consumer-oriented and business-to-business Web Services like .NET My Services is great, although there are serious and well-founded concerns about security and privacy. 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 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 WSDL (Web Services Description Language)
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.

Figure 6-1.Windows Distributed interNet Architecture

 

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

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 WSDL (Web Services Description Language). 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 datatype 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[1] 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. 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.[2] 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 directory. 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:[3]

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

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, and transaction mode. 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 objects exposed in the WebService base class. From this point, you can use these ASP objects as if you were developing an ASP-based application.[4] 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 Web Service 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[5] 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).

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 could to disable this flag so the web server doesn't have to generate and manage session IDs for each user accessing this web method. It might improve performance. This flag is true by default.

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.

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 as follows:[6]

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

HTTP GET Consumer

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

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

Figure 6-3.An autogenerated Web Services consumer

 

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

The following is a sample HTTP GET request and response. The placeholders shown need to
be replaced with actual values.
 
GET /PubsWS/PubsWS.asmx/GetAuthor?sSSN=string HTTP/1.1
Host: localhost
 
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 132438length
 
<?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:[8]

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

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

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

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

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

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

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

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

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

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

csc TestProxy.cs /r:PubsWS.dll

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

Figure 6-4.C# Web Service client after calling GetAuthors( )

 

Here is the VB Web Service client, TestProxyVB.vb:

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

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

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

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

Non-.NET Consumers

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

Web method

Protocol

GetBooks( )

HTTP GET protocol

GetAuthor(ssn)

HTTP POST protocol

GetBooksByAuthor(ssn)

SOAP protocol

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

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

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

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

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

Figure 6-5.VB client form to test Web Services

 

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

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

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

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

Figure 6-6.VB client form after calling GetBooks

 

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

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

Figure 6-7.VB client form after calling GetAuthor

 

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

<html version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<head><title>A list of books</title></head>
<style>
.hdr { background-color=#ffeedd; font-weight=bold; }
</style>
<body>
<B>List of books</B>
<table style="border-collapse:collapse" border="1">
<tr>
  <td class="hdr">Title</td>
  <td class="hdr">Type</td>
  <td class="hdr">Price</td>
  <td class="hdr">Notes</td>
</tr>
<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 & "\templateAuthorTitle.xsl"
    
  ' Transform the XML document into an HTML document
  myWebBrowser.Document.Write oDOM.transformNode(oXSL)
  myWebBrowser.Document.Close
 
  Set oXSL = Nothing
  Set oDOM = Nothing
  Set oXMLHTTP = Nothing
End Sub

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

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

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

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

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

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

Figure 6-8.VB client form after calling GetBooksByAuthor

 

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

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

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

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.

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 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 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 Secure Sockets Layer (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 have to have one parameter as the token. A simple pseudocode example of a bank account Web Service can be as follows:

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.

The Microsoft .NET Cryptographic Services can be very useful if you choose this route. DES, RC2, TripleDES, and RSA encryption/decryption 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 implemeneted in different languages, but they can even be on different platforms. Because of this greater interoperability, Web Services are eminently suitable for business-to-business (B2B) integration.


Footnotes:

1. Current Microsoft .NET SOAP implementation runs on top of HTTP.

2. If you use Visual Studio .NET to create your Web Service, the discovery file is created automatically.

3. VS.NET uses vsdisco as the extension for its dynamic discovery files.

4. Access to the Request and Response objects through the Context property of the WebService class.

5. 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 MachineName\IUSR_MachineName, so make sure to grant this user (or whatever userName you specify) database access.

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

7. A simple Reflection example can be found in the section "Attribute-Based Programming" in Chapter 4.

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

9. You can find the proxy source file under Web References\ReferenceName as reference.cs (if you're working with C#). If you have not renamed the reference name, it is localhost by default.

  Contact Us | E-mail Us | Site Guide | About PerfectXML | Advertise ©2004 perfectxml.com. All rights reserved. | Privacy