W eb services allow access to software components through standard web protocols such as HTTP and SMTP. Using the Internet and XML, we can now create software components that communicate with others, regardless of language, platform, or culture. Until now, software developers have progressed toward this goal by adopting proprietary componentized software methodologies, such as DCOM; however, because each vendor provides its own interface protocol, integration of different vendors’ components is a nightmare. By substituting the Internet for proprietary transport formats and adopting standard protocols such as SOAP, web services help software developers create building blocks of software, which can be reused and integrated regardless of their location.
In this chapter, we describe the .NET web services architecture and provide examples of a web service provider and several web service consumers.
You may have heard the phrase “software as a service” and wondered about its meaning. The term service, in day-to-day usage, refers to what you get from a service provider. For example, you bring your dirty clothing to a cleaner to use its cleaning service. Software, on the other hand, is commonly thought of as an application, either an off-the-shelf product, or a custom application developed by a software firm. You typically buy the software (or in our case, build the software). It usually resides on some sort of media such as floppy diskette or CD and is sold in a shrink-wrapped package through retail outlets, or, in the case of a web application, the software application is not distributed, but is accessed through a browser.
How can software be viewed as a service? The example we are about to describe might seem far-fetched; however, it is possible with current technology. Imagine the following. As you grow more comfortable with the Internet, you might choose to replace your computer at home with something like an Internet Device, a large-screen PDA designed for use with the Internet. Let’s call the device an iDev. Let’s suppose that with this device, you can be on the Internet immediately through your cell phone, WiFi, or some other means. When you want to do word processing, your iDev is configured to print to a Microsoft Word service somewhere in Redmond, so you can type away without the need to install word-processing software. When you are done, the document can be saved at an iStore server where you can later retrieve it. Notice that for you to do this, the iStore server must host a software service that allows you to store documents. Microsoft might charge you a service fee based on the amount of time your word processor is running and the features you use (such as the grammar and spell checkers). The iStore service charges might vary based on the size of your document and how long it is stored. Of course, none of these charges would come in the mail, but rather through an escrow service where the money would be withdrawn from your bank account or credit card.
As long as your document is in a standard format, such as XML, you’re free to switch word processors at any time. Of course, the document that you store at the iStore server is already in a standard data format. Since iStore utilizes the iMaxSecure software service from a company called iNNSA (Internet Not National Security Agency), the security of your files is assured. And because you use the document storage service at iStore, you also benefit from having your document authenticated and decrypted upon viewing, as well as encrypted at storing time.
While this particular vision of software as a service has yet to be
realized, a variety of for-fee and free services have begun to
appear. In early 2001, Microsoft announced plans for an integrated
collection of consumer-oriented services (known first by their
codename,
“Hailstorm,” and later as “.NET
My Services”) but was forced to abandon the
initiative for a variety of reasons, some technical and others legal,
political, or market-related. Today, Microsoft offers a variety of
user-centric services for identification and authentication, email,
instant messaging, automated alerts, calendar, address book, and
personal information storage. These are available through its MSN
online services, and through Passport (http://www.passport.net
), Alerts (http://alerts.microsoft.com
), MSN Wallet
(http://wallet.msn.com
), and
Hotmail.
Of greater interest to developers, however, is the availability of these services for use as building blocks in third-party web applications. Hosted by Microsoft and known as Microsoft .NET Services, Passport, Alerts, and MSN Wallet can each be licensed and incorporated into any application that adheres to XML web services standards, regardless of platform. A more recently announced web service is Microsoft MapPoint .NET, which is a set of services that allows you to incorporate maps, driving directions, distance calculations, proximity searches, and other location intelligence into your applications.
In addition to Microsoft, other companies are beginning to offer information and functionality as services over the Web. A recent poster child is Saleforce.com, which offers customer relations management (CRM) software over the Web, either as a standalone product or as a set of services that can be incorporated into third-party applications. The Liberty Alliance is at work defining an authentication service that can be offered as an alternative to Microsoft Passport. And both Google and Amazon now make portions of their business information available through public web service interfaces.
The potential for consumer-oriented and business-to-business web services like Microsoft .NET Services is great, although there are serious and well-founded concerns about security and privacy. In the mean time, web services can be great in interoperability areas where there are needs to expose legacy functionalities or to enable interaction between multiple heterogeneous systems. In one form or another, though, web services are here to stay, so let’s dive in and see what’s underneath.
Web services 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 are distributed software components accessible through standard web protocols. The first part of the definition is similar to that for COM/DCOM components. However, it is the second part that distinguishes web services from the crowd. Web services enable software to interoperate with a much broader range of clients. While COM-aware clients can understand only COM components, web services can be consumed by any application that understands how to parse an XML-formatted stream transmitted through HTTP channels. XML is the key technology used in web services and is used in the following areas of the Microsoft .NET web services framework:
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.
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.
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.
You may have heard the phrase “DCOM is COM over the wire.” Web services are similar to DCOM except that the wire is no longer a proprietary communication protocol. With web services, the wire formats rely on more open Internet protocols such as HTTP or SMTP.
A web service is more or less a stateless component running on the web server, exposed to the world through standard Internet protocols. Microsoft .NET web services currently supports three protocols: HTTP GET, HTTP POST, and SOAP over HTTP (Simple Object Access Protocol), explained in the next sections. Because these protocols are standard protocols for the Web, it is very easy for the client applications to use the services provided by the server.
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.
Similar to HTTP GET and HTTP POST, SOAP serves as a mechanism for passing messages between the clients and servers. In this context, the clients are web services consumers, and the servers are the web services. The clients simply send an XML-formatted request message to the server to get the service over an HTTP channel. The server responds by sending back yet another XML-formatted message. The SOAP specification describes the format of these XML requests and responses. It is simple, yet it is extensible, because it is based on XML.
SOAP is different than HTTP GET and HTTP POST because it uses XML to format its payload. The messages being sent back and forth have a better structure and can convey more complex information compared to simple name/value pairs in HTTP GET/POST protocols. Another difference is that SOAP can be used on top of other transport protocols, such as SMTP in addition to HTTP.[1]
For web service clients to understand how to interact with a web service, there must be a description of the method calls, or the interface that the web service supports. This web service description document is found in an XML schema called Web Services Description Language (WSDL) . Remember that type libraries and IDL scripts are used to describe a COM component. Both IDL and WSDL files describe an interface’s method calls and the list of in and out parameters for the particular call. The only major difference between the two description languages is that all descriptions in the WSDL file are done in XML.
In theory, any WSDL-capable SOAP client can use the WSDL file to get a description of your web service. It can then use the information contained in that file to understand the interface and invoke your web service’s methods.
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:
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.
An abstract set of operations supported by one or more endpoints.
An abstract description of an action supported by the service. Each
operation specifies the input and output messages defined as
<message>
elements.
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.
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).
A single endpoint defined by associating a binding and a network address. In other words, it describes the protocol and data-format specification to be used as well as the network address of where the web service clients can bind to for the service.
The following shows a typical WSDL file structure:
<definitions name="" targetNamespace="" xmlns: . . . > <types> . . . </types> <message name=""> . . . </message> . . . <portType name=""> <operation name=""> <input message="" /> <output message="" /> </operation> . . . </portType> . . . <binding name=""> <protocol
:binding . . . > <operation name=""> <protocol
:operation . . . > <input> . . . </input> <output> . . . </output> </operation> . . . </binding> . . . <service name=""> <port name="" binding=""> <protocol
:address location="" /> </port> . . . </service> </definitions>
The
<types>
element
contains physical type descriptions defined in XML Schema (XSD).
These types are being referred to from the
<message>
elements.
For each of the web methods in the web service, there are two
messages defined for a particular port: input and output. This means
if a web service supports all three protocols: SOAP, HTTP GET, and
HTTP POST, there will be six <message>
elements defined, one pair for each port. The naming convention used
by the Microsoft .NET autogenerated WSDL is:
MethodName + Protocol + {In, Out}
For example, a web method called GetBooks( ) has the following messages:
<message name="GetBooksSoapIn"> . . . </message> <message name="GetBooksSoapOut"> . . . </message> <message name="GetBooksHttpGetIn"> . . . </message> <message name="GetBooksHttpGetOut"> . . . </message> <message name="GetBooksHttpPostIn"> . . . </message> <message name="GetBooksHttpPostOut"> . . . </message>
For each protocol that the web service supports, there is one
<portType>
element defined. Within each <portType>
element, all operations are specified as
<operation>
elements. The naming convention for the port type is:
WebServiceName + Protocol
To continue our example, here are the port types associated with the web service that we build later in this chapter, PubsWS:
<portType name="PubsWSSoap"> <operation name="GetBooks"> <input message="GetBooksSoapIn" /> <output message="GetBooksSoapOut" /> </operation> </portType> <portType name="PubsWSHttpGet"> <operation name="GetBooks"> <input message="GetBooksHttpGetIn" /> <output message="GetBooksHttpGetOut" /> </operation> </portType> <portType name="PubsWSHttpPost"> <operation name="GetBooks"> <input message="GetBooksHttpPostIn" /> <output message="GetBooksHttpPostOut" /> </operation> </portType>
We have removed namespaces from the example to make it easier to read.
While the port types are abstract operations for each port, the
bindings provide concrete information on what protocol is being used,
how the data is being transported, and where the service is located.
Again, there is a
<binding>
element for each protocol supported by the web service:
<binding name="PubsWSSoap" type="s0:PubsWSSoap"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="GetBooks"> <soap:operation soapAction="http://tempuri.org/GetBooks" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> </binding> <binding name="PubsWSHttpGet" type="s0:PubsWSHttpGet"> <http:binding verb="GET" /> <operation name="GetBooks"> <http:operation location="/GetBooks" /> <input> <http:urlEncoded /> </input> <output> <mime:mimeXml part="Body" /> </output> </operation> </binding> <binding name="PubsWSHttpPost" type="s0:PubsWSHttpPost"> <http:binding verb="POST" /> <operation name="GetBooks"> <http:operation location="/GetBooks" /> <input> <mime:content type="application/x-www-form-urlencoded" /> </input> <output> <mime:mimeXml part="Body" /> </output> </operation> </binding>
For SOAP protocol, the binding is
<soap:binding>
, and the transport is SOAP
messages on top of HTTP protocol. The
<soap:operation>
element defines the HTTP
header soapAction
, which points to the web method.
Both input and output of the SOAP call are SOAP messages.
For the HTTP GET and HTTP POST protocols, the binding is
<http:binding>
with the verb being GET and
POST, respectively. Because the GET and POST verbs are part of the
HTTP protocol, there is no need for the extended HTTP header (like
soapAction
for SOAP protocol). The only thing we
need is the URL that points to the web method; in this case, the
<soap:operation>
element contains the
attribute location, which is set to /GetBooks
.
The only real difference between the HTTP GET and POST protocols is
the way the parameters are passed to the web server. HTTP GET sends
the parameters in the query string, while HTTP POST sends the
parameters in the form data. This difference is reflected in the
<input>
elements of the operation GetBooks
for the two HTTP protocols. For the HTTP GET protocol, the input is
specified as <http:urlEncoded
/>
, whereas for the HTTP POST protocol, the
input is <mime:content
type="application/x-www-form-urlencoded
"
/>
.
Looking back at the template of the WSDL document, we see that the
only thing left to discuss is the <service>
element, which defines the ports supported by this web service. For
each of the supported protocol, there is one
<port>
element:
<service name="PubsWS"> <port name="PubsWSSoap" binding=s0:PubsWSSoap"> <soap:address location="http:// . . . /PubsWs.asmx" /> </port> <port name="PubsWSHttpGet" binding="s0:PubsWSHttpGet"> <http:address location="http:// . . . /PubsWs.asmx" /> </port> <port name="PubsWSHttpPost" binding="s0:PubsWSHttpPost"> <http:address location="http:// . . . /PubsWs.asmx" /> </port> </service>
Even though the three different ports look similar, their
binding
attributes associate the address of the
service with a binding
element defined earlier.
Web service clients now have enough information on where to access
the service, through which port to access the web service method, and
how the communication messages are defined.
Although it is possible to read the WSDL and manually construct the HTTP[2] conversation with the server to get to a particular web service, there are tools that autogenerate client-side proxy source code to do the same thing. We show such a tool in “web services Consumers” later in this chapter.
Even though advertising of a web service is important, it is optional. Web services can be private as well as public. Depending on the business model, some business-to-business (B2B) services would not normally be advertised publicly. Instead, the web service owners would provide specific instructions on accessing and using their service only to the business partner.
To advertise web services publicly, authors post discovery files on the Internet. Potential web services clients can browse to these files for information about how to use the web services—the WSDL. Think of it as the yellow pages for the web service. All it does is point you to where the actual web services reside and to the description of those web services.
The process of looking up a service and checking out the service description is called web service discovery. Currently in .NET, there are two ways of advertising the service: static and dynamic. In both of these, XML conveys the locations of web services.
Static discovery is easier to understand because it is explicit in nature. If you want to advertise your web service, you must explicitly create the .disco discovery file and point it to the WSDL.[3]
All .disco files contain a root element
discovery
, as shown
in the following code sample. Note that discovery
is in the namespace
http://schemas.xmlsoap.org/disco/
, which is
referred to as disco
in this sample.
<?xml version="1.0" ?> <disco:discovery xmlns:disco="http://schemas.xmlsoap.org/disco/"> </disco:discovery>
Inside the discovery
element, there can be one or
more of
contractRef
or
discoveryRef
elements. Both of these elements are described in the namespace
http://schemas.xmlsoap.org/disco/scl/
. The
contractRef
tag is used to reference an actual web
service URL that would return the WSDL or the description of the
actual web service contract. The discoveryRef
tag,
on the other hand, references another discovery document.
This XML document contains a link to one web service and a link to another discovery document:
<?xml version="1.0" ?> <disco:discovery xmlns:disco="http://schemas.xmlsoap.org/disco/" xmlns:scl="http://schemas.xmlsoap.org/disco/scl/"> <scl:contractRef ref="http://yourWebServer/yourWebService.asmx?WSDL
"/>
<scl:discoveryRef ref="http://yourBrotherSite/hisWebServiceDirectory.disco
"/>
</disco:discovery>
This sample disco file specifies two different
namespaces: disco
, which is a nickname for the
namespace http://schemas.xmlsoap.org/disco/
; and
scl
, short for
http://schemas.xmlsoap.org/disco/scl/
. The
contractRef
element specifies the URL where
yourWebService
WSDL can be obtained. Right
below that is the discoveryRef
element, which
links to the discovery file on
yourBrotherSite
web site. This linkage
allows for structuring networks of related discovery documents.
As opposed to explicitly specifying the URL for all web services your site supports, you can enable dynamic discovery, which enables all web services underneath a specific URL on your web site to be listed automatically. For your web site, you might want to group related web services under many different directories and then provide a single dynamic discovery file in each of the directories. The root tag of the dynamic discovery file is dynamicDiscovery instead of discovery:
<?xml version="1.0" encoding="utf-8"?> <dynamicDiscovery xmlns="urn://schemas-dynamic:disco.2000-03-17" />
You can optionally specify exclude paths so that the dynamic mechanism does not have to look for web services in all subdirectories underneath the location of the dynamic discovery file. Exclude paths are in the following form:
<exclude path="pathname" />
If you run IIS as your web server, you’d probably have something like the following for a dynamic discovery file:[4]
<?xml version="1.0" encoding="utf-8"?> <dynamicDiscovery xmlns="urn://schemas-dynamic:disco.2000-03-17"> <exclude path="_vti_cnf" /> <exclude path="_vti_pvt" /> <exclude path="_vti_log" /> <exclude path="_vti_script" /> <exclude path="_vti_txt" /> <exclude path="Web References" /> </dynamicDiscovery>
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.
Universal Description, Discovery, and Integration (UDDI) Business Registry is like a yellow pages of web services. It allows businesses to publish their services and locate web services published by partner organizations so that they can conduct transactions quickly, easily, and dynamically with their trading partner.
Through UDDI APIs, businesses can find services over the web that match their criteria (e.g., cheapest fare), that offer the service they request (e.g., delivery on Sunday), and so on. Currently backed by software giants such as Microsoft, IBM, and Ariba, UDDI is important to web services because it enables access to businesses from a single place.[5]
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:
The two essential classes for creating web services are the WebService base class and WebMethodAttribute. We make use of these classes in the next section, where we implement a web service provider and several web service consumers. WebService is the base class from which all web services inherit. It provides properties inherent to legacy ASP programming such as Application, Server, Session, and a new property, Context, which now includes Request and Response.
The WebMethodAttribute class allows you to apply attributes to each public method of your web service. Using this class, you can assign specific values to the following attributes: description, session state enabling flag, message name, transaction mode, and caching. See the following section for an example of attribute setting in C# and VB.
The WebServiceAttribute class is used to provide more attributes about the web service itself. You can display a description of the web service, as well as the namespace to which this web service belongs.
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.
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:
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.NET objects exposed in the WebService base class such as
Application, Session, Server, Request, and Response.[6] It is highly recommended that
you specify a namespace for your web service before publishing it
publicly because the default namespace, http://tempuri.org/
, will not uniquely
identify your web service from other web services. To do this, tag
the WebService class with the Namespace attribute, specifying your
own namespace.
Tag the public methods with WebMethod attributes to make web methods— public methods of a distributed component that are accessible via the Web. You don’t have to tag a method as WebMethod unless you want that method to be published as a web method.
The following C# code demonstrates a simple web service[7] that exposes four methods to Internet clients.
We emphasize “Internet” because anyone that can access this asmx file on the web server can access these methods, as opposed to your COM component, which can be accessed only by COM clients:
<%@ WebService Language="C#" Class="PubsWS.PubsWS" %> namespace PubsWS { using System; using System.Data; using System.Data.OleDb; using System.Web; using System.Web.Services; [WebService(Namespace="http://Oreilly/DotNetEssentials/")] public class PubsWS : WebService { private static string m_sConnStr = "provider=sqloledb;server=(local);database=pubs; Integrated Security=SSPI"; [WebMethod(Description="Returns a DataSet containing all authors.")] public DataSet GetAuthors( ) { OleDbDataAdapter oDBAdapter; DataSet oDS; oDBAdapter = new OleDbDataAdapter("select * from authors", m_sConnStr); oDS = new DataSet( ); oDBAdapter.Fill(oDS, "Authors"); return oDS; } [WebMethod] public DataSet GetAuthor(string sSSN) { OleDbDataAdapter oDBAdapter; DataSet oDS; oDBAdapter = new OleDbDataAdapter( "select * from authors where au_id ='" + sSSN + "'", m_sConnStr); oDS = new DataSet( ); oDBAdapter.Fill(oDS, "SelectedAuthor"); return oDS; } [WebMethod(MessageName="GetBooksByAuthor", Description="Find books by author's SSN.")] public DataSet GetBooks(string sAuthorSSN) { OleDbDataAdapter oDBAdapter; DataSet oDS; oDBAdapter = new OleDbDataAdapter( "select * from titles inner join titleauthor on " + "titles.title_id=titleauthor.title_id " + "where au_id='" + sAuthorSSN + "'", m_sConnStr); oDS = new DataSet( ); oDBAdapter.Fill(oDS, "Books"); oDBAdapter = new OleDbDataAdapter("select * from authors " + "where au_id='" + sAuthorSSN + "'", m_sConnStr); oDBAdapter.Fill(oDS, "Author"); return oDS; } [WebMethod] public DataSet GetBooks( ) { OleDbDataAdapter oDBAdapter; DataSet oDS; oDBAdapter = new OleDbDataAdapter("select * from titles" , m_sConnStr); oDS = new DataSet( ); oDBAdapter.Fill(oDS, "Books"); return oDS; } } // End PubsWS }
If you are familiar with ASP, you may recognize the usage of the @
symbol in front of keyword WebService
. This
WebService directive specifies the language of the web service so
that ASP.NET can compile the web service with the correct compiler.
This directive also specifies the class that implements the web
service so it can load the correct class and employ reflection to
generate the WSDL for the web service.
Because PubsWS also uses ADO.NET’s OLE DB provider for its data-access needs, we have to add a reference to System.Data and System.Data.OleDb, in addition to the System, System.Web, and System.Web.Services namespaces.
Class PubsWS inherits from WebService with the colon syntax that should be familiar to C++ or C# developers:
public class PubsWS : WebService
The four methods that are tagged with WebMethod attributes are
GetAuthors( ), GetAuthor( ), GetBooks(string),
and GetBooks( ). In C#, you can tag public methods with a WebMethod
attribute using the []
syntax. In VB, you must use
<>
. For example, in VB, the second method
would be declared as:
<WebMethod( )> Public Function GetAuthor(sSSN As String) As DataSet
By adding [WebMethod]
in front of your public
method, you make the public method callable from any Internet client.
What goes on behind the scenes is that your public method is
associated with an attribute, which is
implemented as a WebMethodAttribute class. WebMethodAttribute has six
properties:
Controls whether or not to buffer the method’s response.
Specifies the length of time in seconds to keep the method response in cache; the default is not to hold the method response in cache (0 seconds). A cache hit is when an identical call with identical parameters is requested. The cached response is used to avoid re-processing.
Provides additional information about a particular web method.
Enables or disables session state. If you don’t want to use session state for the web method, you should make sure that this flag is disabled so the web server doesn’t have to generate and manage session IDs for each user accessing the method.
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).
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)
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:
Basic Forms authentication is where unauthenticated requests are redirected to a login form.
Authentication is performed by IIS in one of three ways: basic, digest, or Integrated Windows Authentication.
Unauthenticated requests to the resource are redirected to Microsoft’s centralized authentication service. When authenticated, a token is passed back and used by subsequent requests.
In v1.1 of .NET Framework, for security reasons, HttpGet and HttpPost protocol are disabled by default. To override the default and enable or disable any particular protocols, the web.config can be modified as the following:
<configuration> . . . <webServices> <protocols> <add name="HttpGet" /> <add name="HttpPost" /> </protocols> </webServices> . . . </configuration>
After creating the web service, you can provide the supporting files to help in the discovery of the service. The static discovery disco file is:[8]
<?xml version="1.0" ?> <disco:discovery xmlns:disco="http://schemas.xmlsoap.org/disco/" xmlns:scl="http://schemas.xmlsoap.org/disco/scl/"> <scl:contractRef ref="http://localhost/PubsWS/PubsWS.asmx?WSDL"/> </disco:discovery>
Now that you have successfully created a web service, let’s take a look at how this web service is used by web clients. Web services clients communicate with web services through standard web protocols. They send and receive XML-encoded messages to and from the web services. This means any application on any platform can access the web services as long as it uses standard web protocols and understands the XML-encoded messages. As mentioned earlier, there are three protocols that the web clients can employ to communicate with the servers (web services): HTTP GET, HTTP POST, and SOAP. We demonstrate next how to build client applications that utilize each of these protocols. These web services-client applications are done in legacy languages such as VB6 and Perl,[9] and .NET languages, such as C# and VB.NET, to demonstrate the cross-language/cross-platform benefits of web services.
Let’s look at how it is done using HTTP GET first, since it is the simplest. In the examples that follow, we use localhost as the name of the web server running the service and PubsWS as the virtual directory. If you have deployed the sample web service on a remote server, you’ll need to substitute the name of the server and virtual directory as appropriate.
If you point your web browser at the web service URL (http://localhost/PubsWS/PubsWS.asmx), it will give you a list of supported methods. To find out more about these methods, click one of them. This brings up a default web service consumer. This consumer, autogenerated through the use of reflection, is great for testing your web services’ methods.[10] It uses the HTTP GET protocol to communicate with the web service. This consumer features a form that lets you test the method (see Figure 6-3), as well as descriptions of how to access the method via SOAP, HTTP GET, or HTTP POST.
Here is the description of the GET request and response supplied by the default consumer:
The following is a sample HTTP GET request and response. The placeholders shown need to be replaced with actual values. GET /PubsWS/PubsWS.asmx/GetAuthor?sSSN=string HTTP/1.1 Host: localhost HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <DataSet xmlns="http://Oreilly/DotNetEssentials/"> <schema xmlns="http://www.w3.org/2001/XMLSchema">schema</schema>xml </DataSet>
Using HTTP GET protocol, the complete URL to invoke the web method, along with parameters, can be the following:
http://localhost/PubsWS/PubsWS.asmx/GetAuthor?sSSN=172-32-1176
Here is the response; including HTTP response headers and the raw XML (note how the response includes the serialized schema and data from the DataSet object):
Cache-Control: private, max-age=0 Date: Tue, 08 May 2001 20:53:16 GMT Server: Microsoft-IIS/5.0 Content-Length: 2450 Content-Type: text/xml; charset=utf-8 Client-Date: Tue, 08 May 2001 20:53:16 GMT Client-Peer: 127.0.0.1:80 <?xml version="1.0" encoding="utf-8"?> <DataSet xmlns="http://Oreilly/DotNetEssentials/"> <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="NewDataSet" msdata:IsDataSet="true"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element name="SelectedAuthor"> <xs:complexType> <xs:sequence> <xs:element name="au_id" type="xs:string" minOccurs="0" /> <xs:element name="au_lname" type="xs:string" minOccurs="0" /> <xs:element name="au_fname" type="xs:string" minOccurs="0" /> <xs:element name="phone" type="xs:string" minOccurs="0" /> <xs:element name="address" type="xs:string" minOccurs="0" /> <xs:element name="city" type="xs:string" minOccurs="0" /> <xs:element name="state" type="xs:string" minOccurs="0" /> <xs:element name="zip" type="xs:string" minOccurs="0" /> <xs:element name="contract" type="xs:boolean" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema> <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> <NewDataSet xmlns=""> <SelectedAuthor diffgr:id="SelectedAuthor1" msdata:rowOrder="0"> <au_id>172-32-1176</au_id> <au_lname>White</au_lname> <au_fname>Johnson</au_fname> <phone>408 496-7223</phone> <address>10932 Bigge Rd.</address> <city>Menlo Park</city> <state>CA</state> <zip>94025</zip> <contract>true</contract> </SelectedAuthor> </NewDataSet> </diffgr:diffgram> </DataSet>
In the section “HTTP GET Consumer,” we saw the automatic creation of a web services consumer by merely hitting the URL of the web services, http://localhost/PubsWS/PubsWS.asmx. It is now time for us to see how a web client can use HTTP POST and SOAP to access a web service. This time around, we are going write a C# web service consumer.
The Microsoft .NET SDK has a rich set of tools to simplify the
process of creating or consuming web services. We are going to use
one of these tools, wsdl
, to generate source code
for the proxies to the actual web services:[11]
wsdl /l:CS /protocol:HttpPost http://localhost/PubsWS/PubsWS.asmx?WSDL
This command line creates a proxy for the PubsWS web service from the
WSDL document from the URL
http://localhost/PubsWS/PubsWS.asmx?WSDL. The
proxy uses HTTP POST as its protocol to talk to the web service; it
is generated as a C# source file. The wsdl
tool
can also take a WSDL file as its input instead of a URL pointing to
the location where the WSDL can be obtained.
This C# proxy source file represents the proxy class for the PubsWS web service that the clients can compile against. This generated C# file contains a proxy class PubsWS that derives from HttpPostClientProtocol class. If you use the /protocol:HttpGet or /protocol:SOAP2[12] parameters, the PubsWS derives from either the HttpGetClientProtocol or SoapHttpClientProtocol class.
After generating the C# source file PubsWS.cs, we have two choices for how this proxy can be used. One way is to include this source file in the client application project using Visual Studio .NET. The project has to be a C# project if you choose this route.[13] To make use of the proxy, you also have to add to your project any references that the proxy depends on. In this example, the necessary references for the proxy file are System.Web.Services, System.Web.Services.Protocols, System.Xml.Serialization, and System.Data.
The other way to use the proxy is more flexible. You can compile the C# source file into a dynamic link library (DLL) and then add a reference to this DLL to any project you want to create. This way you can even have a VB project use the DLL.
Below is the command line used to compile the C# proxy source into a DLL. Notice that the three references are linked to PubsWS.cs so that the resulting PubsWS.DLL is self-contained (type the entire command on one line):
csc /t:library /r:system.web.services.dll /r:system.xml.dll /r:system.data.dll PubsWS.cs
Regardless of how you choose to use the proxy, the client application
code will still look the same. Consider the next two code examples
containing C# and VB code. For both languages, the first lines create
an instance of the proxy to the web service, PubsWS. The second lines
invoke the GetAuthors web method to get a DataSet as the result. The
remaining lines bind the default view of the table
Authors
to the data grid, add the data grid to a
form, and display the form. Note that these examples use the Windows
Forms API, which we’ll discuss in Chapter 8. Here is the C# web service client,
TestProxy.cs:
using System; using System.Drawing; using System.Windows.Forms; using System.Data; public class TestProxy { public static void Main( ) { /* Create a proxy. */ PubsWS oProxy = new PubsWS( ); /* Invoke GetAuthors( ) over HTTPPOST and get the data set. */ DataSet oDS = oProxy.GetAuthors( ); /* Create a data grid and connect it to the data set. */ DataGrid dg = new DataGrid( ); dg.Size = new Size(490, 270); dg.DataSource = oDS.Tables["Authors"].DefaultView; /* Set the properties of the form and add the data grid. */ Form myForm = new Form( ); myForm.Text = "DataGrid Sample"; myForm.Size = new Size(500, 300); myForm.Controls.Add(dg); /* Display the form. */ System.Windows.Forms.Application.Run(myForm); } }
If you created the DLL as previously directed, you can compile this with the following command:
csc TestProxy.cs /r:PubsWS.dll
This creates the executable TestProxy.exe, which gets a DataSet using a HTTP POST call, and displays a data grid containing that dataset. Figure 6-4 shows the output of the C# client after obtaining the data from the PubsWS web service via HTTP POST protocol.
Here is the VB web service client, TestProxyVB.vb:
imports System imports System.Drawing imports System.Windows.Forms imports System.Data Module TestProxyVB Sub Main( ) ' Create a proxy. dim oProxy as PubsWS = new PubsWS( ) ' Invoice GetAuthors( ) over SOAP and get the data set. dim oDS as DataSet = oProxy.GetAuthors( ) ' Create a data grid and connect it to the data set. dim dg as DataGrid = new DataGrid( ) dg.Size = new Size(490, 270) dg.DataSource = oDS.Tables("Authors").DefaultView ' Set the properties of the form and add the data grid. dim myForm as Form = new Form( ) myForm.Text = "DataGrid Sample" myForm.Size = new Size(500, 300) myForm.Controls.Add(dg) ' Display the form. System.Windows.Forms.Application.Run(myForm) End Sub End Module
You can compile the VB web service client with this command (type the entire command on one line):
vbc TestProxyVB.vb /r:System.Drawing.dll /r:System.Windows.Forms.dll /r:System.Data.dll /r:PubsWS.dll /r:System.Web.Services.dll /r:System.dll /r:System.xml.dll
Instead of using wsdl
to generate and include the
proxy in your application, you can also rely on VS.NET to automate
the whole process. In VS.NET, you can just add a Web Reference to
your application. The process of adding a Web Reference to an
application involves the discovery of the web service, obtaining the
WSDL, generating the proxy, and including the proxy into the
application.[14]
This section shows how to develop non-.NET web service consumers using HTTP GET, HTTP POST, and SOAP protocols. Because we cannot just create the proxy class from the WSDL and compile it with the client code directly, we must look at the WSDL file to understand how to construct and interpret messages between the web service and the clients. We trimmed down the WSDL file for our PubsWS web service to show only types, messages, ports, operations, and bindings that we actually use in the next several web service-client examples. In particular, we will have our VB6 client access the following:
Web method |
Protocol |
GetBooks( ) |
HTTP GET protocol |
GetAuthor(ssn) |
HTTP POST protocol |
GetBooksByAuthor(ssn) |
SOAP protocol |
As a reference, here is the simplified version of the WSDL file while you experiment with the VB6 client application:
<?xml version="1.0" encoding="utf-8"?> <definitions xmlns: . . . xmlns:s0="http://Oreilly/DotNetEssentials/" targetNamespace="http://Oreilly/DotNetEssentials/" > <types> <!-- This data type is used by the HTTP POST call --> <s:element name="GetAuthor"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="sSSN" type="s:string" /> </s:sequence> </s:complexType> </s:element> <!-- This data type is used by the HTTP POST call --> <s:element name="GetAuthorResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="GetAuthorResult""> <s:complexType> <s:sequence> <s:element ref="s:schema" /> <s:any /> </s:sequence> </s:complexType> </s:element> </s:sequence> </s:complexType> </s:element> <!-- This data type is used by the SOAP call --> <s:element name="GetBooksByAuthor"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="sAuthorSSN" type="s:string" /> </s:sequence> </s:complexType> </s:element> <!-- This data type is used by the SOAP call --> <s:element name="GetBooksByAuthorResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="GetBooksByAuthorResult""> <s:complexType> <s:sequence> <s:element ref="s:schema" /> <s:any /> </s:sequence> </s:complexType> </s:element> </s:sequence> </s:complexType> </s:element> <!-- This data type is used by the HTTP GET call --> <s:element name="GetBooks"> <s:complexType /> </s:element> <!-- This data type is used by the HTTP GET call --> <s:element name="GetBooksResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="GetBooksResult"> <s:complexType> <s:sequence> <s:element ref="s:schema" /> <s:any /> </s:sequence> </s:complexType> </s:element> </s:sequence> </s:complexType> </s:element> <!-- This data type is used by the HTTP GET/POST responses --> <s:element name="DataSet" <s:complexType> <s:sequence> <s:element ref="s:schema" /> <s:any /> </s:sequence> </s:complexType> </s:element> </types> <!-- These messages are used by the SOAP call --> <message name="GetBooksByAuthorSoapIn"> <part name="parameters" element="s0:GetBooksByAuthor" /> </message> <message name="GetBooksByAuthorSoapOut"> <part name="parameters" element="s0:GetBooksByAuthorResponse" /> </message> <!-- These messages are used by the HTTP GET call --> <message name="GetBooksHttpGetIn" /> <message name="GetBooksHttpGetOut"> <part name="Body" element="s0:DataSet" /> </message> <!-- These messages are used by the HTTP POST call --> <message name="GetAuthorHttpPostIn"> <part name="sSSN" type="s:string" /> </message> <message name="GetAuthorHttpPostOut"> <part name="Body" element="s0:DataSet" /> </message> <!-- SOAP port --> <portType name="PubsWSSoap"> <operation name="GetBooks"> <documentation>Find books by author's SSN.</documentation> <input name="GetBooksByAuthor" message="s0:GetBooksByAuthorSoapIn" /> <output name="GetBooksByAuthor" message="s0:GetBooksByAuthorSoapOut" /> </operation> </portType> <!-- HTTP GET port --> <portType name="PubsWSHttpGet"> <operation name="GetBooks"> <input message="s0:GetBooksHttpGetIn" /> <output message="s0:GetBooksHttpGetOut" /> </operation> </portType> <!-- HTTP POST port --> <portType name="PubsWSHttpPost"> <operation name="GetAuthor"> <input message="s0:GetAuthorHttpPostIn" /> <output message="s0:GetAuthorHttpPostOut" /> </operation> </portType> <!-- SOAP binding --> <binding name="PubsWSSoap" type="s0:PubsWSSoap"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="GetBooks"> <soap:operation soapAction="http://Oreilly/DotNetEssentials/GetBooksByAuthor" style="document" /> <input name="GetBooksByAuthor"> <soap:body use="literal" /> </input> <output name="GetBooksByAuthor"> <soap:body use="literal" /> </output> </operation> </binding> <!-- HTTP GET binding --> <binding name="PubsWSHttpGet" type="s0:PubsWSHttpGet"> <http:binding verb="GET" /> <operation name="GetBooks"> <http:operation location="/GetBooks" /> <input> <http:urlEncoded /> </input> <output> <mime:mimeXml part="Body" /> </output> </operation> </binding> <!-- HTTP POST binding --> <binding name="PubsWSHttpPost" type="s0:PubsWSHttpPost"> <http:binding verb="POST" /> <operation name="GetAuthor"> <http:operation location="/GetAuthor" /> <input> <mime:content type="application/x-www-form-urlencoded" /> </input> <output> <mime:mimeXml part="Body" /> </output> </operation> </binding> <!-- The whole web service and address bindings --> <service name="PubsWS"> <port name="PubsWSSoap" binding="s0:PubsWSSoap"> <soap:address location="http://localhost/PubsWS/PubsWS.asmx" /> </port> <port name="PubsWSHttpGet" binding="s0:PubsWSHttpGet"> <http:address location="http://localhost/PubsWS/PubsWS.asmx" /> </port> <port name="PubsWSHttpPost" binding="s0:PubsWSHttpPost"> <http:address location="http://localhost/PubsWS/PubsWS.asmx" /> </port> </service> </definitions>
In both the HTTP GET and HTTP POST protocols, you pass parameters to the web services as name/value pairs. With the HTTP GET protocol, you must pass parameters in the query string, whereas the HTTP POST protocol packs the parameters in the body of the request package. To demonstrate this point, we will construct a simple VB client using both HTTP GET and HTTP POST protocols to communicate with the PubsWS web service.
Let’s first create a VB6 standard application. We need to add a reference to Microsoft XML, v3.0 (msxml3.dll), because we’ll use the XMLHTTP object to help us communicate with the web services. For demonstrative purposes, we will also use the Microsoft Internet Controls component (shdocvw.dll) to display XML and HTML content.
First, add two buttons on the default form, form1
,
and give them the captions GET
and
POST
, as well as the names
cmdGet
and cmdPost
,
respectively. After that, drag the WebBrowser object from the toolbar
onto the form, and name the control myWebBrowser
.
If you make the WebBrowser navigate to about:blank
initially, you will end up with something like Figure 6-5.
Now all we need is some code similar to the following to handle the two buttons’ click events:
Private Sub cmdGet_Click( ) Dim oXMLHTTP As XMLHTTP Dim oDOM As DOMDocument Dim oXSL As DOMDocument ' Call the web service to get an XML document Set oXMLHTTP = New XMLHTTP oXMLHTTP.open "GET",_ "http://localhost/PubsWS/PubsWS.asmx/GetBooks", _ False oXMLHTTP.send Set oDOM = oXMLHTTP.responseXML ' Create the XSL document to be used for transformation Set oXSL = New DOMDocument oXSL.Load App.Path & "templateTitle.xsl" ' Transform the XML document into an HTML document and display myWebBrowser.Document.Write CStr(oDOM.transformNode(oXSL)) myWebBrowser.Document.Close Set oXSL = Nothing Set oDOM = Nothing Set oXMLHTTP = Nothing End Sub Private Sub cmdPost_Click( ) Dim oXMLHTTP As XMLHTTP Dim oDOM As DOMDocument Dim oXSL As DOMDocument ' Call the web service to get an XML document Set oXMLHTTP = New XMLHTTP oXMLHTTP.open "POST", _ "http://localhost/PubsWS/PubsWS.asmx/GetAuthor", _ False oXMLHTTP.setRequestHeader "Content-Type", _ "application/x-www-form-urlencoded" oXMLHTTP.send "sSSN=172-32-1176" Set oDOM = oXMLHTTP.responseXML ' Create the XSL document to be used for transformation Set oXSL = New DOMDocument oXSL.Load App.Path & " emplateAuthor.xsl" ' Transform the XML document into an HTML document and display myWebBrowser.Document.Write oDOM.transformNode(oXSL) myWebBrowser.Document.Close Set oXSL = Nothing Set oDOM = Nothing Set oXMLHTTP = Nothing End Sub
The two subroutines are similar in structure, except that the first one uses the HTTP GET protocol and the second one uses the HTTP POST protocol to get to the PubsWS web service. Let’s take a closer look at what the two subroutines do.
For the HTTP GET protocol, we use the XMLHTTP object to point to the
URL for the web method, as specified in the WSDL. Since the GetBooks
web method does not require any parameters, the query string in this
case is empty. The method is invoked synchronously because the async
parameter to XMLHTTP’s open
method is set to false
. After the method
invocation is done, we transform the XML result using
templateTitle.xsl and display the HTML on the
myWebBrowser
instance on the form. Figure 6-6 displays the screen of our web services
testing application after invoking the GetBooks web method at URL
http://localhost/PubsWS/ PubsWS.asmx/ through
HTTP GET protocol.
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.
The following code is the XSL used to transform the XML result from the GetBooks web method call to HTML to be displayed on the web browser instance on the VB form:
<html version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl"> <head><title>A list of books</title></head> <style> .hdr { background-color=#ffeedd; font-weight=bold; } </style> <body> <B>List of books</B> <table style="border-collapse:collapse" border="1"> <tr> <td class="hdr">Title</td> <td class="hdr">Type</td> <td class="hdr">Price</td> <td class="hdr">Notes</td> </tr> <xsl:for-each select="//Books"> <tr> <td><xsl:value-of select="title"/></td> <td><xsl:value-of select="type"/></td> <td><xsl:value-of select="price"/></td> <td><xsl:value-of select="notes"/></td> </tr> </xsl:for-each> </table> </body> </html>
Here is the XSL used to transform the XML result from the GetAuthor web method call to HTML to be displayed on the web browser instance on the VB form:
<html version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl"> <head><title>Selected author</title></head> <STYLE> .hdr { background-color:'#ffeedd'; text-align:'right'; vertical-align:'top'; font-weight=bold; } </STYLE> <body> <B>Selected author</B> <xsl:for-each select="//SelectedAuthor"> <table style="border-collapse:'collapse'" border="1"> <tr><td class="hdr">ID</td> <td><xsl:value-of select="au_id"/></td></tr> <tr><td class="hdr">Name</td> <td><xsl:value-of select="au_fname"/> <xsl:value-of select="au_lname"/></td></tr> <tr><td class="hdr">Address</td> <td><xsl:value-of select="address"/><br> <xsl:value-of select="city"/>, <xsl:value-of select="state"/> <xsl:value-of select="zip"/></br></td></tr> <tr><td class="hdr">Phone</td> <td><xsl:value-of select="phone"/></td></tr> </table> </xsl:for-each> </body> </html>
We can also use SOAP protocol to access the web service. Because the web service is exposed through HTTP and XML, any clients on any platform can access the service as long as they conform to the specification of the service. Again, this specification is the WSDL file. By inspecting the WSDL file—specifically, the SOAP section—we can use XMLHTTP again to communicate in SOAP dialog. Let’s see how this can be done.
Let’s go back to the example of consumer web
services using VB6 and XMLHTTP. Add another button on the form, and
call it cmdSOAP
with caption
SOAP
. This time, we will ask the web service to
return all books written by a particular author:
Private Sub cmdSOAP_Click( ) Dim oXMLHTTP As XMLHTTP Dim oDOM As DOMDocument Dim oXSL As DOMDocument ' Call the web service to get an XML document Set oXMLHTTP = New XMLHTTP oXMLHTTP.open "POST", "http://localhost/PubsWS/PubsWS.asmx", False Dim sBody As String sBody = "" & _ "<soap:Envelope" & _ " xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""" & _ " xmlns:xsd=""http://www.w3.org/2001/XMLSchema""" & _ " xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" & _ "<soap:Body>" & _ "<GetBooksByAuthor xmlns=""http://Oreilly/DotNetEssentials/"">" & _ "<sAuthorSSN>213-46-8915</sAuthorSSN>" & _ "</GetBooksByAuthor>" & _ "</soap:Body>" & _ "</soap:Envelope>" oXMLHTTP.setRequestHeader "Content-Type", "text/xml" oXMLHTTP.setRequestHeader "SOAPAction", "http://Oreilly/DotNetEssentials/GetBooksByAuthor" oXMLHTTP.send sBody Set oDOM = oXMLHTTP.responseXML ' Create the XSL document to be used for transformation Set oXSL = New DOMDocument oXSL.Load App.Path & " emplateAuthorTitle.xsl" ' Transform the XML document into an HTML document myWebBrowser.Document.Write oDOM.transformNode(oXSL) myWebBrowser.Document.Close Set oXSL = Nothing Set oDOM = Nothing Set oXMLHTTP = Nothing End Sub
This method is structurally similar to the ones used for HTTP GET and
HTTP POST; however, it has some very important differences. In SOAP,
you have to set the Content-Type
to
text/xml
instead of
application/x-www-form-urlencoded
as for the HTTP
POST. By this time, it should be clear to you that only HTTP POST and
SOAP care about the Content-Type
because they send
the data in the body of the HTTP request. The HTTP GET protocol does
not really care about the Content-Type
because all
of the parameters are packaged into the query string. In addition to
the difference in format of the data content, you also have to refer
to the WSDL to set the SOAPAction
header variable
to the call you want. Looking back at the SOAP section of the WSDL,
if you want to call the GetBooks(sAuthorSSN)
method of the web service, you will set the
SOAPAction
header variable to
http://Oreilly/DotNetEssentials/GetBooksByAuthor
.
On the other hand, if you want to call the GetBooks( ) method
instead, the SOAPAction
variable has to be set to
http://Oreilly/DotNetEssentials/GetBooks
. The
reason the namespace is
http://Oreilly/DotNetEssentials/
is because we set
it up as the attribute of the PubsWS web service class.
After setting up the header variables, pass the parameters to the server in the body of the message. While HTTP POST passes the parameters in name/value pairs, SOAP passes the parameters in a well-defined XML structure:
<soap:Envelope . . . namespace omitted . . . > <soap:Body> <GetBooksByAuthor xmlns="http://Oreilly/DotNetEssentials/"> <sAuthorSSN>213-46-8915</sAuthorSSN> </GetBooksByAuthor> </soap:Body> </soap:Envelope>
Both the SOAP request and response messages are packaged within a
Body
inside an Envelope
. With
the previously specified request, the response SOAP message looks
like this:
<?xml version="1.0"?>
<soap:Envelope . . . namespace omitted . . . >
<soap:Body>
<GetBooksByAuthorResult xmlns="http://Oreilly/DotNetEssentials/">
<result>
<xsd:schema id="NewDataSet" . . . >
< . . . content omitted . . . >
</xsd:schema>
<NewDataSet xmlns="">
<Books>
<title_id>BU1032</title_id>
<title>The Busy Executive's Database Guide</title>
< . . . more . . . >
</Books>
<Books>
<title_id>BU2075</title_id>
<title>You Can Combat Computer Stress!</title>
< . . . more . . . >
</Books>
<Author>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<phone>415 986-7020</phone>
<address>309 63rd St. #411</address>
<city>Oakland</city>
<state>CA</state>
<zip>94618</zip>
<contract>True</contract>
</Author>
</NewDataSet>
</result>
</GetBooksByAuthorResult>
</soap:Body>
</soap:Envelope>
Figure 6-8 shows the result of the test form after invoking the GetBooksByAuthor web method using the SOAP protocol.
The XSL stylesheet used for transformation of the resulting XML to HTML is included here for your reference. Notice that since GetBooksByAuthor returns two tables in the dataset, author and books, we can display both the author information and the books that this author wrote:
<html version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl"> <head><title>A list of books</title></head> <style> .hdr { background-color=#ffeedd; font-weight=bold; } </style> <body> <B>List of books written by <I><xsl:value-of select="//Author/au_fname"/> <xsl:value-of select="//Author/au_lname"/> (<xsl:value-of select="//Author/city"/>, <xsl:value-of select="//Author/state"/>) </I> </B> <table style="border-collapse:collapse" border="1"> <tr> <td class="hdr">Title</td> <td class="hdr">Type</td> <td class="hdr">Price</td> <td class="hdr">Notes</td> </tr> <xsl:for-each select="//Books"> <tr> <td><xsl:value-of select="title"/></td> <td><xsl:value-of select="type"/></td> <td><xsl:value-of select="price"/></td> <td><xsl:value-of select="notes"/></td> </tr> </xsl:for-each> </table> </body> </html>
It’s ok to go through the previous exercise to understand how to write a VB application as a front-end client for web services. However, for your real-life applications, you should use a SOAP toolkit (such as the Microsoft SOAP toolkit) to make things easier.
The next couple of examples take a step further. This time, we will use Perl to access our sample web service PubWS with the help of ActiveState Perl and SOAP::Lite Perl Library (author: Paul Kulchenko).[15]
With SOAP::Lite Perl Library, all you have to do is to create a SOAP::Lite object (similar to the proxy object in the C# example) and setup its uri and proxy properties. Once this is done, you can ask the proxy object to run the remote web methods. The uri property maps to the first part of the soapAction and the web method name maps to the second part. The proxy property maps to the physical location of the web service itself.
In our first Perl example, we want to call the GetAuthors web method. This method does not take any parameter and returns a DataSet object. The SOAP portion of the WSDL states that the soapAction for this method is http://Oreilly/DotNetEssentials/GetAuthors. By default, SOAP::Lite library constructs the soapAction as <uri string> + “#” + <web method name>. This does not agree with web services written on the .NET Framework, where the soapAction is uri_string + “/” + web method name. Fortunately, the SOAP::Lite library provides a callback-like kind of feature so that we can plug in a sub-routine to override the default construction of the soapAction string. The highlighted line of PERL script below basically just concatenates the uri string and the web method name. For simplicity, we rely on the fact that the uri string already has the trailing slash.[16]
To run the program, just type perl <program name>:
use SOAP::Lite
on_action => sub {sprintf '%s%s', @_};
my $proxy = SOAP::Lite
-> uri('http://Oreilly/DotNetEssentials/')
-> proxy('http://localhost/PubsWS/PubsWS.asmx'),
my $method = SOAP::Data->name('GetAuthors')
->attr({xmlns => 'http://Oreilly/DotNetEssentials/'});
my $xmlDataSet = $proxy->call($method);
# my $xmlDataSet = $proxy->GetAuthors( ); # You can also do this
my @authorsNodes = $xmlDataSet->valueof('//Authors'),
foreach $author (@authorsNodes) {
print $author->{'au_lname'}, ", ", $author->{'au_fname'}, "
";
print " ", $author->{'address'}, "
";
print " ", $author->{'city'}, ", ",
$author->{'state'}, " ", $author->{'zip'}, "
";
print " ", $author->{'phone'}, "
";
}
The output of this program is the following:
White, Johnson 10932 Bigge Rd. Menlo Park, CA 94025 408 496-7223 Green, Marjorie 309 63rd St. #411 Oakland, CA 94618 415 986-7020 . . .
Once the $proxy object is created, we call the GetAuthors web method knowing that the generated soapAction will be `http://Oreilly/DotNetEssentials/' + `GetAuthors’ and the location of the web service where SOAP::Lite library will try to contact is http://localhost/PubsWS/PubsWS.asmx. Because this web method returns a DataSet, which translates to an XML document, we can parse it with XPATH-like syntax to obtain the list of authors and traverse the list to display the information for each author.
Now we want to call the GetBooksByAuthor web method as the second Perl program. This method takes one string parameter, the SSN for the author, and returns a DataSet object containing the author’s name and all books he has published. The SOAP portion of the WSDL document states that the soapAction for this method is named http://Oreilly/DotNetEssentials/GetBooksByAuthor, and the parameter is named sAuthorSSN:
use SOAP::Lite on_action => sub { sprintf '%s%s', @_ }; my $proxy = SOAP::Lite -> uri('http://Oreilly/DotNetEssentials/') -> proxy('http://localhost/PubsWS/PubsWS.asmx'), my $method = SOAP::Data->name('GetBooksByAuthor') ->attr({xmlns => 'http://Oreilly/DotNetEssentials/'}); my @params = (SOAP::Data->name(sAuthorSSN => '998-72-3567')); my $xmlDataSet = $proxy->call($method => @params); my $author = $xmlDataSet->valueof('//Author'), print "Books by author: "; print $author->{'au_lname'}, ", ", $author->{'au_fname'}, " "; print $author->{'address'}, " "; print $author->{'city'}, ", ", $author->{'state'}, " ", $author->{'zip'}, " "; print $author->{'phone'}, " "; my @books = $xmlDataSet->valueof('//Books'), foreach $book (@books) { print "Type : ", $book->{'type'}, " "; print "Title : ", $book->{'title'}, " "; print "Price : $", $book->{'price'}, " "; print "Notes : ", $book->{'notes'}, " "; print " "; }
Type perl
yourPerlFile.pl
to
run the program. The output is:
Books by author: Ringer, Albert 67 Seventh Av. Salt Lake City, UT 84152 801 826-0752 Type : psychology Title : Is Anger the Enemy? Price : $10.95 Notes : Carefully researched study of the effects of strong emotions on the body. Metabolic charts included. Type : psychology Title : Life Without Fear Price : $7 Notes : New exercise, meditation, and nutritional techniques that can reduce the shock of daily interactions. Popular audience. Sample menus included, exercise video available separately.
As you can see, you can easily use any type of web service client to access a .NET web service. Clients of web services need to know how to communicate in HTTP and understand the WSDL. By the same token, you can develop a web service in any language and on any platform as long as it adheres to its WSDL specification.
Now that you have seen how simple it is to create and consume web services, we should let you in on a small but important point—that XML web services is not the solution for everything. As responsible developers, we have to evaluate the requirements as well as the applicability of certain technology before applying it. On one hand, it’s great to use web services as a way to expose legacy functionalities, or to enable integration of disparate systems on different locations, different platforms, or different companies. But there is no real reason to make a web service out of something that can be implemented as a simple component, or through .NET Remoting, which is discussed in Chapter 4. The decision is up to you. Again, the technology is just a tool; how you use it is another story.
All of the web service client examples we’ve used so far make a call to the web service and wait for data before continuing work. While this approach might be appropriate for small, quick calls, such synchronous calls might yield an unacceptable response time. The web service has to perform a time-consuming task. Asynchronous web service calls are the answer in this case. This section explores how the .NET Framework web services enable clients to call web methods asynchronously, yielding a better overall response time for an application.
If you look back at the generated source for the web service proxy in
the previous section, you will find that for every web method there
are three separate proxy methods generated. The following shows the
three proxy methods: GetAuthors( )
,
BeginGetAuthors( )
, and EndGetAuthors(
)
:
public System.Data.DataSet GetAuthors( ) { object[] results = this.Invoke("GetAuthors", new object[0]); return ((System.Data.DataSet)(results[0])); } public System.IAsyncResult BeginGetAuthors(System.AsyncCallback callback, object asyncState) { return this.BeginInvoke("GetAuthors",new object[0],callback,asyncState); } public System.Data.DataSet EndGetAuthors(System.IAsyncResult asyncResult) { object[] results = this.EndInvoke(asyncResult); return ((System.Data.DataSet)(results[0])); }
In the earlier web service consumer example, we’ve
used the proxy method GetAuthors( )
to call the
web method on the other side of the HTTP channel. The following
example shows how the other two methods can be used to perform an
asynchronous web method call.
As it turns out, there are a couple of different ways to use the
Begin
MethodName
(
)
and
End
MethodName
(
)
to call the web method asynchronously. We will show you
each one and describe how they are different from one another.
The first way will be calling
Begin
MethodName
(
)
passing in null
for both the callback
and the asyncState objects. We will poll on the IAsyncResult object
until it is completed while simulating other works. When the call is
done, we call
End
MethodName
(
)
to complete the web method call and obtain the DataSet
result:[17]
WSPubsWS.PubsWS oProxy = new WSPubsWS.PubsWS( ); IAsyncResult result; // Async call polling (cannot run this in debugger) Console.Write("Async Polling"); result = oProxy.BeginGetAuthors(null, null); while(!result.IsCompleted) { Console.Write(" . . . "); Thread.Sleep(5); } DataSet oDSAsync = oProxy.EndGetAuthors(result); oDSAsync.WriteXml("outputpolling.xml"); Console.WriteLine("done");
Although this might not be the best way, it demonstrates one way of performing an asynchronous call to a web method.
The second way still does not use the callback mechanism. Be patient. Again, we use the IAsyncResult object to perform a block waiting on the remote call. Block waiting put the current thread to sleep, giving up the CPU to other processes or threads until the wait handle is signaled. Depending on your application, this might be how you would want to do it:
// Async call with waithandle Console.WriteLine("Async, processing then wait for handle"); result = oProxy.BeginGetAuthors(null, null); Console.WriteLine(" . . . Processing some more . . . "); result.AsyncWaitHandle.WaitOne( ); // block on handle DataSet oDSAsyncWait = oProxy.EndGetAuthors(result); oDSAsyncWait.WriteXml("outputWaiting.xml"); Console.WriteLine("done");
The third and fourth ways use the callback mechanism so we will know
exactly when to complete the web method call with the
End
MethodName
(
)
. We make the call to the
Begin
MethodName
(
)
passing in the callback delegate. When the web method
call completes, we will get called back. Since this example is done
as a console application, a little more setting up is required;
otherwise, the application might exit before the callback occurs.
This is done through the AutoResetEvent object (a synchronization
primitive similar to a mutex). Basically, we want to set up a wait
object, call the async web method, and wait for the signal from the
callback before continuing with the application:
// Async call with callback 1 AutoResetEvent oWait = new AutoResetEvent(false); Console.WriteLine("Async, processing then wait for callback"); CallBack cb = new CallBack(oProxy, oWait); Result =oProxy.BeginGetAuthors(new AsyncCallback(cb.CallBackMethod), null); Console.WriteLine(" . . . Processing some more . . . "); Console.WriteLine("Application waits for callback rendezvous"); oWait.WaitOne( ); Console.WriteLine("done");
The CallBack class needs to have the proxy to complete the web method call. It also needs the AutoResetEvent to signal the main application flow to continue. The following is the definition of the CallBack class at this moment:
public class CallBack { public CallBack(WSPubsWS.PubsWS oProxy, AutoResetEvent oWait) { m_oProxy = oProxy; m_oWait = oWait; } public void CallBackMethod(IAsyncResult result) { DataSet oDSAsyncCB; oDSAsyncCB = m_oProxy.EndGetAuthors(result); oDSAsyncCB.WriteXml("outputCallback.xml"); m_oWait.Set( ); } private WSPubsWS.PubsWS m_oProxy; private AutoResetEvent m_oWait; }
In the next example, we pass both the callback and the asyncState
object to the
Begin
MethodName
(
)
method. We already know all about the callback object.
What about this asyncState thing? It actually can be
anything you want to pass to the callback
object. We will pass the proxy object itself via this asyncState
object so we will know how to complete the web method call when the
callback is invoked. The following code segment shows how this is
done:
// async call with callback 2 oWait.Reset( ); Console.WriteLine("Async, processing then wait for callback also"); CallBack cbWithData = new CallBack(oWait); result=oProxy.BeginGetAuthors(new AsyncCallback(cbWithData.CallBackMethod), oProxy); Console.WriteLine("Processing some more data"); Console.WriteLine("Application waits for callback rendezvous"); oWait.WaitOne( ); Console.WriteLine("done");
This time, the CallBack object is instantiated with only one
parameter. Changes to the CallBack class are highlighted in the
following code block. When the callback method is invoked, you can
cast the IAsyncResult object’s AsyncState back to
whatever type you passed into the second parameter of the
Begin
MethodName
(
)
method. In this case, we cast the AsyncState object back
to the proxy in order to complete the web method call with
End
MethodName
(
)
to obtain the resulting DataSet:
public class CallBack { public CallBack(WSPubsWS.PubsWS oProxy, AutoResetEvent oWait) { m_oProxy = oProxy; m_oWait = oWait; }public CallBack(AutoResetEvent oWait) {
m_oWait = oWait;
}
public void CallBackMethod(IAsyncResult result) { DataSet oDSAsyncCB;if(m_oProxy == null) {
WSPubsWS.PubsWS oTmp = (WSPubsWS.PubsWS)result.AsyncState;
oDSAsyncCB = oTmp.EndGetAuthors(result);
oDSAsyncCB.WriteXml("outputCallbackWithData.xml");
} else {
oDSAsyncCB = m_oProxy.EndGetAuthors(result); oDSAsyncCB.WriteXml("outputCallback.xml");}
m_oWait.Set( );
} private WSPubsWS.PubsWS m_oProxy; private AutoResetEvent m_oWait; }
In a Windows Form application, you can start the web service call via a button clicked or a menu clicked and still can have other parts of the application be responsive to the user. We will leave that example to you as an exercise.
As you have seen from the web service consumer using SOAP, the data being passed back and forth as is structured XML inside the body of the HTTP package. In particular it has the following format:
<Envelope> <Header> < . . . > </Header> <Body> < . . . > </Body> </Envelope>
We have seen how to pass data from the client to the web service through the Body of the Envelope. In this section, we show how to use the Header of the Envelope.
Through this optional header node, the developers can pass
information that does not relate to any particular web method or
information that is common to all web methods. Of course, you can
pass information that does not related to any particular web method
in a method call itself, such as
InitiateService(param
) enforcing a usage rule that
this method has to be called first. This is not an elegant solution.
Just add the param
of the InitiateService to the
header. On the other hand, if all web methods of your service require
a common parameter, wouldn’t it be nice if you could
just set up the header and didn’t have to worry
about this parameter for each of the web methods. Examples of header
information are a security token, a payment token, priority, and so
on; there is no reason to pass these pieces of common information via
every web method.
Once you construct your web service to use a SOAP header, the WSDL will instruct the client that a header node is in place so that a web service client knows how to set up the header information before making a call to the web service methods. The following example should provide some clarification:
<%@ WebService Language="C#" Class="TestHeader.PayWS" %> namespace TestHeader { using System; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; public class Payment : SoapHeader { public string CreditCardNumber; } [WebService(Namespace="http://Oreilly/DotNetEssentials/")] public class PayWS : WebService { public Payment clientPayment; [WebMethod, SoapHeader("clientPayment")] public string Task1( ) { return string.Format("Task1 performed. " + "Charging $25,000.00 on credit card: {0}", clientPayment.CreditCardNumber); } [WebMethod, SoapHeader("clientPayment")] public string Task2( ) { return string.Format("Task2 performed. " + "Charging $4,500.00 on credit card: {0}", clientPayment.CreditCardNumber); } } }
In this example, we create a web service with two methods, Task1 and Task2. Both of these methods use the Payment object in the soap header.
The SOAP request and response format for Task1 follows:
POST /SOAPHeader/PayWS.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://Oreilly/DotNetEssentials/Task1" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header>
<Payment xmlns="http://Oreilly/DotNetEssentials/">
<CreditCardNumber>string</CreditCardNumber>
</Payment>
</soap:Header>
<soap:Body> <Task1 xmlns="http://Oreilly/DotNetEssentials/" /> </soap:Body> </soap:Envelope> HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <Task1Response xmlns="http://Oreilly/DotNetEssentials/"> <Task1Result>string</Task1Result> </Task1Response> </soap:Body> </soap:Envelope>
This is different than the soap envelope we’ve seen for PubsWS where there was no SOAP header. In this case, the header is an object of type Payment and this Payment class has a string property called CreditCardNumber.
Our example expects the clients to this web service to pass in the Payment object. The following is the client code. Of course, you will have to generate the proxy class for the PayWS web service using wsdl.exe and compile this proxy along with the client code:
public class ClientWS { public static void Main( ) { // Create a proxy PayWS oProxy = new PayWS( ); // Create the payment header. Payment pmt = new Payment( ); pmt.CreditCardNumber = "1234567890123456"; // Attach the payment header to the proxy. oProxy.PaymentValue = pmt; // Call Task1. System.Console.WriteLine(oProxy.Task1( )); // Call Task2. System.Console.WriteLine(oProxy.Task2( )); } }
The output of the client:
Task1 performed. Charging $25,000.00 on credit card: 1234567890123456 Task2 performed. Charging $4,500.00 on credit card: 1234567890123456
The above is a trivial usage of SOAP header. In reality, the SOAP headers are probably not as simple as our Payment class.
Microsoft, IBM, and a number of other companies have been working on the Global XML web services Architecture (GXA) that defines a framework on how to build standardized web services incorporating security, reliability, referral, routing, transaction, and so on. A number of GXA specifications use SOAP Header as the means for this infrastructure.
This section demonstrates how to incorporate security into your web service. We will do so in two ways: system security and application security. System-level security allows for restricting access to the web services from unauthorized clients. It is done in a declarative fashion, whereas application-level security is more flexible. With system-level security, you will most likely have the list of authorized clients’ IP addresses that you will let access your web service through the use of some configuration-management tools. With application-level security, you will incorporate the authentication into your web service, thus providing a more flexible configuration.[18]
Because web services communication is done through HTTP, you can apply system-level security on web services just as you do for other web pages or resources on your web site.
There are a number of different ways you can secure your web services. For a business-to-business (B2B) solution, you can use the IIS Administration Tool to restrict or grant permission to a set of IP addresses, using the Internet Protocol Security (IPSec) to make sure that the IP address in the TCP/IP header is authenticated. When you rely only on the client to provide the IP in the TCP/IP header, hackers can still impersonate other host IPs when accessing your web services. IPSec authenticates the host addresses using the Kerberos authentication protocol. You can also use a firewall to restrict access to your web services for your partner companies. For a business-to-consumer (B2C) scenario, you can take advantage of the authentication features of the HTTP protocol.
To show you how to use the authentication feature of the HTTP
protocol to secure your web services, let’s revisit
the example web service we have in this chapter, PubsWS. All we have
to do to secure PubsWS web service is go to the
IIS Admin
Tool and choose to edit the File
Security
properties for the PubsWS.asmx. Instead of
keeping the default setting, which leaves this file accessible to all
anonymous users, we change this setting to “Basic
Authentication” only, which means unchecking
“Anonymous Access” and checking
only “Basic Authentication” in the
Authenticated Access frame. After this change, only users that pass
the authentication can make use of the web service.
For real-life situations, of course, we are not going to use just the Basic Authentication method, because it sends the username and password in clear text through the HTTP channel. We would choose other methods, such as Secure Sockets Layer (SSL) underneath Basic Authentication, so that the data passed back and forth is secure. Available methods include:
Sends the username and password to the Web Server in clear text. IIS authenticates the login against the database of users for the domain.
Similar to Basic Authentication, except that the username and password are sent with SSL encryption.
Uses a hashing technique, as opposed to SSL encryption, to send client credentials securely to the server.
Good for intranet scenarios only. Uses the login information of the client for 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.
A less systematic way of securing your web services involves taking security into your own hands. You can program your web services so that all of their methods require an access token, which can be obtained from the web service after sending in the client’s username and password. The client credentials can be sent to the server through SSL, which eliminates the risk of sending clear-text passwords across the wire. Through this SSL channel, the server returns an access token to the caller, who can use it to invoke all other web service methods. Of course, all of the other web methods that you publish must have one parameter as the token. A simple pseudocode example of a bank account web service can be the following:
web service Bank Account Web Methods: Login(user id, password) returns access token or nothing Deposit(access token, account number, amount, balance) returns boolean Withdraw(access token, account number, amount, balance) returns boolean
The only method that should be on SSL is the Login method. Once the token is obtained, it can be used for other web methods. Of course, you should be able to make sure that subsequent calls using this token are coming from the same IP as the Login( ) call. You can also incorporate an expiration timestamp on this access token to ensure that the token only exists in a certain time frame until a renewal of the access token is needed.
You can also use public/private keys (asymmetric) encryption for better key- management. The following scenario might suit your needs.
The client gets the server’s public key and uses it to encrypt the requesting data (possibly including the client’s private symmetric encryption key) before calling the web service. This ensures that the requesting data is encrypted and only the server can decrypt it. The server decrypts the data using the private key, figures out what the request is and uses the client’s private key to encrypt the response before sending it back to the client. This time, the response data is encrypted. The client then decrypts the response package with its private symmetric key to view clear data. Since asymmetric cryptography operations are always much slower than symmetric, it’s probably best if you only use asymmetric cryptography for key distribution. Once the key is communicated, you can use the symmetric operation for better performance. Rolling your own security is always harder than using a standard security solution. There are numerous areas you will have to manage yourself such as identity authentication, message authentication, and so on. Be forewarned if you choose this route.
The Microsoft .NET Cryptographic Services can be very useful if you choose to apply application security for your web services. DES, RC2, RC4, TripleDES, and RSA cryptography algorithms are supported along with hashing methods such as SHA and MD5. These implementations in the .NET library enable developers to avoid low-level grunt work and focus on the application logic.
In this chapter, we’ve introduced you to the new paradigm of applications—the enterprise application. You are no longer restricted to homogeneous platforms for implementing your solutions. With Microsoft web services, your solutions can span many different platforms because the communication between web services is done through standard Internet protocols such as HTTP and XML. The distributed components in Windows DNA with which you may be familiar are now replaced by web services. Using web services as components in a distributed environment allows for a heterogeneous system. The web services in your system can not only be implemented in different languages, but can even be on different platforms. Because of this greater interoperability, web services are eminently suitable for B2B integration.
[1] SOAP is recognized as the cross-platform standard protocol to use for web services both inside and outside the Microsoft circle.
[2] Current Microsoft .NET SOAP implementation runs on top of HTTP.
[3] If you use Visual Studio .NET to create your web service, the discovery file is created automatically.
[4] VS.NET uses vsdisco as the extension for its dynamic discovery files.
[5] UDDI SDK can be downloaded from Microsoft as a .NET assembly.
[6] Access to the Request and Response objects can be done through the Context property of the WebService class.
[7] For security reasons, the current release of ASP.NET runs as the account ASPNET. If you are using integrated security to access database resources, you must grant database access to the ASPNET account. You can also enable impersonation in the web.config or machine.config file:
<system.web> <identity impersonate="true" userName="." password=""/> </system.web>
If you set impersonate to true but leave UserName and password blank, the application will run as MachineNameIUSR_MachineName, so make sure to grant this user (or whatever userName you specify) database access.
[8] This code snippet assumes the virtual directory you set up is /PubsWS on your local web server.
[9] We use SOAP::Lite PERL modules. See
http://www.soaplite.com/
.
[10] A simple reflection example can be found in Section 4.3.1.3.
[11] wsdl.exe generates the proxy source code similar to the way IDL compilers generate source files for DCOM proxies. The only difference is that WSDL is the language that describes the interface of the software component, which is XML-based.
[12] In .NET Framework 1.1, SOAP 1.2 is also supported via /protocol:SOAP12. All this does is generate the SOAP derived proxy for SOAP Version 1.2.
[13] For other languages, use wsdl with the /l option to specify the language. See Appendix D for more details.
[14] You can find the proxy source file under Web ReferencesReferenceName as reference.cs (if you’re working with C#). If you have not renamed the reference name, it is localhost by default. (You might have to select the option to “show all files” in VS.NET Solution Explorer.)
[15]
http://www.ActiveState.com/
and http://www.soaplite.com/
,
respectively.
[16] The trace for the SOAP::Lite library turns out to be extremely
useful because it shows the SOAP request and response as well as
other vital information. Insert trace => all
at
the end of the first line of the example.
[17] The proxy in this example was generated with a namespace “WSPubsWS” instead of the default global namespace.
[18] As always, consult your security expert on this decision.
3.15.147.215