.NET Framework Essentials
By Thuan Thai & Hoang Lam
June 2001
0-596-00165-7, Order Number: 1657
320 pages, $29.95
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,
shrink-wrapped, 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 of 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. For sites using the Passport authentication
service, it's no longer necessary to memorize or track numerous
username/password pairs.
Recently, Microsoft also announced Project 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 HailStorm 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 want to get to your contact list in 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 HailStorm Web 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, HailStorm
will help you publish your update in one action.
The potential for consumer-oriented and business-to-business
Web Services like HailStorm 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. It extends distributed computing to
broader ranges of client applications. The best thing is that it does it by
seamlessly marrying and enhancing existing technologies.
Web Services Architecture
Web Services are distributed software components that are
accessible through standard web protocols. The first part of that definition
is similar to that of 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 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( ) will have 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 both 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, specified at http://schemas.xmlsoap.org/disco;
and scl, which points to http://schemas.xmlsoap.org/disco/scl, where the
schema for the service discovery and service contract language is described.
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" ?>
<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:
<?xml version="1.0" ?>
<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" />
</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:
- 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.
- WebServicesConfiguration
- Information needed for the Web Service runtime.
- WebServicesConfigurationSectionHandler
- Information needed for the Web Service runtime.
The two most important classes in the System.Web.Services
namespace 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. In
this book, we do not discuss helper classes dealing with the runtime of web
services.
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 make sure you've read Chapter 5 before attempting
the examples.
Creating a web service is a three-step process.
- 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.
- 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.[3]
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, all you have to do is to tag the web service class with the
Namespace attribute, specifying your own namespace.
- 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 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;uid=sa;pwd=;";
[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:
Public Function <WebMethod( )> 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
intend to use session state for the web method, you might want to disable
this flag so that the web server does not have to generate and manage
session IDs for each user accessing this web method. This 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 might 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. The only thing we recommend
definitively is to 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 would probably 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.
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.[4]
It uses the HTTP GET protocol to communicate with the web service. This
consumer features a form that lets you test the method (see Figure
6-3), as well as descriptions of how to access the method via SOAP, HTTP
GET, or HTTP POST.
Figure 6-3. An autogenerated web services
consumer
|
|
Here is the description of the GET request and response
supplied by the default consumer:
The following is a sample HTTP GET request and response. The placeholders shown need to be replaced with actual values.
GET /PubsWS/PubsWS.asmx/GetAuthor?sSSN=string HTTP/1.1
Host: localhost
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<DataSet xmlns="http://tempuri.org/">
<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://tempuri.org/">
<xsd:schema id="NewDataSet"
targetNamespace="" xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="NewDataSet" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="SelectedAuthor">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="au_id"
msdata:DefaultValue="NULL" type="xsd:string"
minOccurs="0" msdata:Ordinal="0" />
<xsd:element name="au_lname"
msdata:DefaultValue="NULL" type="xsd:string"
minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="au_fname"
msdata:DefaultValue="NULL" type="xsd:string"
minOccurs="0" msdata:Ordinal="2" />
<xsd:element name="phone"
msdata:DefaultValue="NULL" type="xsd:string"
minOccurs="0" msdata:Ordinal="3" />
<xsd:element name="address"
msdata:DefaultValue="NULL" type="xsd:string"
minOccurs="0" msdata:Ordinal="4" />
<xsd:element name="city"
msdata:DefaultValue="NULL" type="xsd:string"
minOccurs="0" msdata:Ordinal="5" />
<xsd:element name="state"
msdata:DefaultValue="NULL" type="xsd:string"
minOccurs="0" msdata:Ordinal="6" />
<xsd:element name="zip"
msdata:DefaultValue="NULL" type="xsd:string"
minOccurs="0" msdata:Ordinal="7" />
<xsd:element name="contract"
msdata:DefaultValue="NULL" type="xsd:boolean"
minOccurs="0" msdata:Ordinal="8" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<NewDataSet xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:updg="urn:schemas-microsoft-com:xml-updategram">
<updg:sync>
<msdata:unchanged>
<SelectedAuthor updg:id="f5237587-4918-44c6-b5cb-51a84e6af4e3"
xmlns="">
<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>
</msdata:unchanged>
</updg:sync>
</NewDataSet>
</DataSet>
HTTP POST Consumer
In the section "HTTP
GET Consumer," we saw the automatic creation of a web services
consumer just by 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 comes with 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:[5]
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 obtained from the
URL http://localhost/PubsWS/PubsWS.asmx?WSDL. The proxy uses HTTP POST as its
protocol to talk to the web service and 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. If you look at this
generated C# file, you will see that it contains a proxy class PubsWS that
derives from HttpPostClientProtocol class. If you use the /protocol:HttpGet or
/protocol:SOAP parameters, then the PubsWS derives from either the
HttpGetClientProtocol or SoapHttpClientProtocol class.
After generating the C# source file PubsWS.cs, we are faced
with 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
GetBooks web method to get a DataSet as the result. The remaining lines bind
the default view of the table Books 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 SOAP and get the data set. */
DataSet oDS = oProxy.GetBooks( );
/* 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["Books"].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 SOAP 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 SOAP protocol.
Figure 6-4. C# web service client after calling
GetBooks( )
|
|
Here is an excerpt from the VB web service client,
TestProxy.vb :
Dim oProxy as PubsWS = New PubsWS( )
Dim oDS as DataSet = oProxy.GetBooks( )
DataGrid1.DataSource = oDS.Tables("Books").DefaultView
You can compile the VB web service client
with this command (type the entire command on one line):
vbc TestProxy.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
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" nillable="true" 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" nillable="true">
<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" nillable="true" 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" nillable="true">
<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" nillable="true">
<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" nillable="true">
<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 sB 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://Oreily/DotNetEssential/"">" & _
"<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 sB
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, we pass the parameters
to the server in the body of the message. Whereas 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. 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 just going to
use 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 T/F
Withdraw(access token, account number, amount, balance) returns T/F
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
application--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. Not
only do the Web Services in your system not have to be implemented in the same
language, they don't even have to be on the same platform. Because of this
greater interoperability, Web Services are very suitable for
business-to-business (B2B) integration.
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. You will have to get to the
Request and Response objects through the Context property of the WebService
class.
4. A simply Reflection example
can be found in the section .
5. 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.
|