Chapter 18. XML Web Services

Topics in This Chapter

  • Web Service Overview: The popularity of Web Services stems from the fact that they are implemented using the non-proprietary technologies of HTTP, XML, and SOAP. For them to become truly useful, users must have a way to locate them on the Internet. UDDI offers one such discovery service.

  • Creating a Web Service: In C#, a Web Service is created by implementing a method that is exposed to clients across the Internet. To indicate a method is available, a WebService directive is required on the Web page, and a WebMethod attribute is attached to the Web Service method.

  • Building an XML Web Service Client: The easiest way to create a Web Service client is to build a proxy using the Web Services Description Language (WSDL) provided by the Web Service. The proxy handles the physical connection to the Web Service and provides asynchronous and synchronous access.

  • WSDL and SOAP: WSDL provides the information that describes a Web Service; Simple Object Access Protocol (SOAP) is the format in which the requests and responses are delivered. An understanding of both offers added insight into how Web Services operate.

  • Web Service Examples: To satisfy real-world business needs, Web Services must deliver more than simple data types. Examples in this section show how to build a service that delivers images and how to build a client to retrieve datasets from the publicly available Amazon Web Services.

XML Web Services provide a relatively simple technique for accessing a method on an object that is running on a local or remote computer. Many embrace this lightweight approach to making Remote Procedure Calls (RPC) as technology that will spell the end to much heavier and complex solutions for distributed communications such as DCOM and CORBA. At the root of its appeal is the fact that Web Services are based on standardized technologies such as HTTP and XML that are designed to promote seamless interoperability among different operating systems. Not everyone is sold on them. Critics point out the lack of security standards, the heavy bandwidth requirements of XML, and the lack of notification and transaction services.

The chapter takes a practical look at the pluses and minuses of implementing and consuming Web Services in a .NET environment. It presents Web Services from the perspective of both the server and client. On the server side, the chapter explores how to define a Web Services class, make its methods available to HTTP clients, and access the ASP.NET Application and Session objects. The emphasis on the client side is how to use the Web Services Description Language (WSDL) contract to create a proxy class that implement calls to the Web Service.

HTTP and SOAP—a protocol that codifies how XML is used to package the request and response data that comprise a Web Service operation—are the two cornerstones of Web Services. Other protocols such as GET and POST are available, but the Simple Object Access Protocol (SOAP) has fewer limitations and is used in the majority of real-world applications. This chapter takes a look at the SOAP format as well as several issues related to SOAP, including the handling of complex data types, exception handling, and security.

We begin the exploration of the .NET Web Services architecture by creating a simple Web Service and Web Service consumer. Our first examples are created “by hand” to emphasize the underlying principles. After these are understood, we see how Visual Studio.NET simplifies the development. The chapter also demonstrates how to use the wsdl.exe tool and .NET classes that are integral to the development process. Closing out the chapter is a sample application to access the Amazon Web Services.

Introduction to Web Services

The Web Service architecture is a service-oriented architecture that enables applications to be distributed across a network or the Internet to clients using any language or operating system. As shown in Figure 18-1, Web Service communications are implemented using existing technology and standards that require no proprietary vendor support. This technology either formed the basis of the Internet or evolved from it. HTTP and XML have been discussed in earlier chapters, but let's take a brief look at them—from a Web Services perspective—along with TCP/IP and SOAP:

  • TCP/IP (Transmission Control Protocol/Internet Protocol). A communications protocol suite that forms the basis of the Internet. It's an open system that governs the flow of data between computers by breaking data into chunks that are easily routed over a network. A Web Service user or developer rarely has direct contact with this layer.

  • HTTP (Hypertext Transfer Protocol). Technically, this text-based protocol is a Remote Procedure Call (RPC) protocol that supports request/response communications. The .NET Framework as well as most production Web Services use it because it has wide support and generally allows information to sail unimpeded through firewalls. Both the HTTP POST and HTTP GET methods are supported in .NET as a way to call a Web Service. Most applications use POST, because it packages the request inside the request body (in a SOAP envelope), rather than as part of a less secure query string.

    Core Note

    Core Note

    The rules that describe how a message is carried within or on top of a protocol is referred to as a binding. The default protocol binding used by .NET for Web Services is HttpSoap, which should be used to access Web Services that understand SOAP. It is specified inside the System.Web section of the machine.config file.

  • XML (Extended Markup Language). We have seen in earlier chapters how data can be serialized into XML format for storage or transmission. The fact that it is text based makes it easy to work with. Just about every programming environment supports tools for encoding and decoding XML formatted data. Its inherent flexibility, extensibility, and validity checking make it attractive to Web Services that must deal with simple data types such as strings, as well as more complex data structures. There are currently two XML-based protocols used for delivering Web Services: XML-RPC and SOAP. Because .NET supports SOAP, this chapter focuses on it. After you understand SOAP, you should have no difficulty with XML-RPC if you encounter it.

  • SOAP (Simple Object Access Protocol). SOAP is defined as “a lightweight protocol for exchange of information in a decentralized, distributed environment.”[1] It is not designed specifically for Web Services, nor restricted to HTTP; but its RPC specifications define a model for invoking methods on a specified target machine, passing parameters, handling faults, and receiving a method response. We will look at SOAP in detail later in the chapter. For now, keep in mind that the details of XML and SOAP are typically handled transparently by .NET. The provider code only needs to focus on implementing the method that returns the desired data; the requestor code simply places a method call and processes the returned data.

Web Service transport

Figure 18-1. Web Service transport

Discovering and Using a Web Service

To use a Web Service, a client must have a description of how to access the service. This information is provided by a Web Services Description Language (WSDL) document that provides the name of the service, the signature of the method(s) that can be called, the address of the service (usually a URL), and binding information that describes how the transport operation will occur. In practical terms, the WSDL information contains the methods that actually call a Web Service. We implement these methods in our client code (as source or a DLL reference) and use them as a proxy to access the service. Section 18.2, “Building an XML Web Service,” provides a concrete example of using .NET to retrieve WSDL information and incorporate it into a client application.

Introduction to UDDI

Web sites are identified by a domain name and IP address that are maintained by a distributed network service known as the Domain Name System (DNS). Servers in this network are responsible for controlling e-mail delivery and translating domain names into IP addresses. The combination of DNS and Web search engines enables users to quickly locate Web content. However, neither DNS servers nor search engines provide a formal way of identifying Web Services.

To organize Web Services into a publicly searchable directory, a public consortium (www.uddi.com) comprising hundreds of companies has defined a standard known as Universal Description Discovery and Integration (UDDI). This standard defines a SOAP-based interface that can be used to publish a service or inquire about services in a UDDI-compliant registry. The registry has a business-to-business flavor about it—containing information about a company, its services, and interface specifications for any Web Services it offers. Importantly, there is no single UDDI registry. IBM, SAP, and Microsoft maintain the most prominent registries. Users may query each separately by entering a business name or service as a search term. Figure 18-2 provides an overview of the inquiry process.

Discovering and accessing a Web Service

Figure 18-2. Discovering and accessing a Web Service

The dialog between the client and UDDI registry server is conducted using SOAP messages. Overall, UDDI defines approximately 40 SOAP messages for inquiry and publishing.

UDDI Discovery Example

To demonstrate how to use UDDI, w'e'll look at the SOAP messages sent between client and server as we seek to discover a Web Service that can provide a stock quote. A Web-based UDDI browser (described shortly) sends and receives the messages.

Step 1: Send Discovery Request

For our example, we'll search the UDDI registry provided by Microsoft:

http://uddi.microsoft.com/inquire

An inquiry may request business information or tModel information. The former contains information about a business, including contact information and a description of services. The tModel structure is much simpler: It consists of a unique key, optional description, and a URL or pointer to a Web page where information for using the service is described. The following message requests a list of tModel keys for companies whose service includes providing a stock quote.

<Envelope>
   <Body>
      <find_tModel generic="1.0" maxRows="100">
         <findQualifiers/>
         <name>stock quote</name>
      </find_tModel>
   </Body>
</Envelope>

Step 2: Registry Service Responds with List of Services Matching Request

A list of tModel keys is returned. These keys are used for the subsequent query:

<soap:Envelope>
  <soap:Body>
    <tModelList generic="1.0" operator="Microsoft Corporation" >
      <tModelInfos>
        <tModelInfo
          tModelKey="uuid:7aa6f610-5e3c-11d7-bece-000629dc0a53">
          <name>Stock Quote</name>
        </tModelInfo>
        <tModelInfo
          tModelKey="uuid:265973ab-31cb-4890-83e0-34d9c1b385e5">
          <name>Stock Quotes and Information</name>
        </tModelInfo>
      </tModelInfos>
    </tModelList>
  </soap:Body>
</soap:Envelope>

Step 3: Retrieve Overview Document Containing WSDL

Send a request for tModel details for the service with the specified tModelKey:

<Envelope>
   <Body>
      <get_tModelDetail generic="1.0">
         <tModelKey>uuid:7aa6f610-5e3c-11d7-bece-000629dc0a53
         </tModelKey>
      </get_tModelDetail>
   </Body>
</Envelope>

The response message includes the OverviewURL element that points to a WSDL document. This document contains the information needed to create an application to access the service.

<overviewDoc>
   <description xml:lang="en">
      Get Stock quote for a company symbol
   </description>
   <overviewURL>
      http://www.webservicex.net/stockquote.asmx?WSDL
   </overviewURL>
</overviewDoc>

You can display the WSDL by pointing your browser to this URL. To invoke the Web Service, remove the query string (?WSDL) from the URL, and navigate to it with your browser.

How to Communicate with a UDDI Service Registry

To interact with a UDDI registry—whether to publish or inquire—you need a way to generate SOAP requests and interpret responses. One option is to write your own application using the Microsoft UDDI 2.0 SDK. It contains excellent C# examples that demonstrate the API and can be run against a test UDDI registry hosted by Microsoft.

For making registry inquires, Web-based UDDI browsers are available. A publicly available one—as well as a wealth of accompanying UDDI information—can be found at

http://www.soapclient.com/UDDISearch.html

Building an XML Web Service

In this section, we demonstrate how to build a Web Service by hand and then access it using a browser. We also show how to create the same Web Service using Visual Studio.NET. Although IIS is used, the examples run on any Web server.

Creating a Web Service by Hand

The first step is to select or create a virtual directory under IIS that will hold the Web Service source code file(s). Any physical directory can be mapped to an IIS virtual directory. You can use the Internet Service Manager or simply right-click the directory and select Sharing-Web Sharing. Then, assign it an alias that will be used in its URL. In our example, we will place the service in the ws subdirectory.

After the directory has been set up, the next step is to use a text editor to create a file in this directory with the .asmx extension to contain the Web Service code. Listing 18-1 contains the code for our simple Web Service. This service exposes a method GetDayBorn that accepts three integer parameters that represent the month, day, and year for birth date. A string value containing the day of the week (Monday, Tuesday, and so on) for this date is returned. The service performs rudimentary error checking and returns an error message if the date is invalid.

Example 18-1. Web Service to Return a Date's Day of Week—BirthDayWS.asmx

<%@ WebService Language="C#" Class="BirthDayWS.BirthDay" %>
using System;
namespace BirthDayWS
{
   public class BirthDay
   {
      [System.Web.Services.WebMethod
      (Description="Return day of week for a date")]
      public string GetDayBorn(int mo, int day, int yr)
      {
         bool err = false;
         string dob;
         if (mo <1 || mo >12) err=true;
         if (day < 1 || day > 31) err=true;
         if (err)
            dob = "Invalid Date";
         } else {
            DateTime dt = new DateTime(yr,mo,day);
            dob = dt.ToString("dddd"); // Get day
         }
         return(dob);
      }
   }
}

The code consists of a single class and a method that is invoked by a client to return the day-of-week value. In addition to the C# code that implements the logic of the service, two other elements are required: a WebService directive and a WebMethod attribute.

WebService Directive

The WebService directive identifies the file as defining a Web Service:

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

The directive specifies the class implementing the XML Web Service and the programming language used in the implementation. In this example, the directive and the code for the class are present in the BirthDayWS.asmx file. Note, however, that the class can be in a separate assembly. In that case, the separate assembly is placed in a in directory below the Web application where the Web Service resides. If the class were in bdAssembly.dll, the WebService directive would look like this:

<%@ WebService Language="C#"
      Class="BirthDayWS.BirthDay, bdAssembly" %>

This statement would be the only line needed in the .asmx file.

WebMethod Attribute

The WebMethod attribute identifies a method as being accessible to clients making HTTP requests—that is, as an XML Web Service. Although not required, it is a good practice to include the Description property to describe the purpose of the method:

[System.Web.Services.WebMethod
       (Description="Return day of week for a date")]

The description is added to the WSDL for the service and—as we will see next—is displayed when a Web Service is accessed via a browser. The WebMethod attribute has other optional parameters, which are described later in this chapter.

Testing the Web Service

A quick way to test the newly developed Web Service is to point a browser to its location. For this example, we enter the address:

http://localhost/ws/BirthDayWS.asmx

This brings up a Web page that lists all the services (methods) available through this .asmx file as well as a Service Description link that displays WSDL information. For our example, there is only one method, GetDayBorn. Clicking it yields the page shown in Figure 18-3. This page contains the name of the class implementing the Web Service, the name of the method to be invoked, a description of the method, and text boxes for entering values to be passed to the Web Service method.

Using a browser to access the BirthDay Web Service

Figure 18-3. Using a browser to access the BirthDay Web Service

To use the Web Service, fill in the parameter values and select the Invoke button. This causes the parameters to be sent to the Web Service using the HTTP POST protocol. The output received from the service is an XML document shown in Figure 18-4.

BirthDayWS output

Figure 18-4. BirthDayWS output

The output from the method is included in the string element of the XML wrapper. Fortunately, we do not have to parse the XML to retrieve this value when writing our own SOAP client. The WSDL contract provides information that allows our client to treat the remote Web Service as a method that returns data conforming to the method's type—not as XML.

Core Note

Core Note

It is unnecessary to compile the .asmx file containing the Web Service in order to deploy it. On a Microsoft Windows server, the ASP.NET runtime automatically compiles the file the first time it is requested and places it in a subdirectory—named after the virtual directory—on the following path:

<%windir%>Microsoft.NETFramework<version>
Temporary ASP.NET Files

To view the WSDL associated with this Web Service, open your browser and append ?WSDL to the URL of the .asmx file:

http://localhost/ws/BirthDayWS.asmx?WSDL

Creating a Web Service Using VS.NET

Aside from the usual advantages of IntelliSense and single key compilation (F5), the major advantage of VS.NET over a manual approach is that it automatically creates a new virtual directory under IIS to host the Web Service. This directory takes the same name as the project.

To create a Web Service with VS.NET, open it up and select ASP.NET Web Service as the project type. Assign it a project name (BirthDayWS, in this case) that reflects the purpose of the Web Service. When the project opens, select View Code and you will find that the following template class is predefined:

namespace BirthDayWS
{
   public class Service1 : System.Web.Services.WebService
   {
      public Service1()
      { InitializeComponent(); }
      private IContainer components = null;
      private void InitializeComponent()   {   }
      protected override void Dispose( bool disposing )
      {
         if(disposing && components != null)
         {
            components.Dispose();
         }
         base.Dispose(disposing);
      }
         // --> Place BirthDayWS code here
   }
}

To implement the service, rename the class to Birthday and add the code for the GetDayBorn method. Use the browser to test the service by either compiling the code, which automatically invokes the browser, or directly pointing the browser to

http://localhost/BirthDayWS/BirthDayWS.asmx

The only significant difference between this code and our handcrafted version is that the Web class now inherits from WebService rather than the default System.Object. The service works either way, but by inheriting from WebService it gains the functionality of an ASP.NET application.

System.Web.Services.WebService Class

The WebService base class exposes properties to a Web Service that enable it to access the intrinsic ASP.NET objects introduced in Chapter 17, “The ASP.NET Application Environment.”

  • ApplicationProvides access to an Application object that can be used to maintain state information for all sessions accessing the Web Service. For example, it can be used to keep track of how many times the service is called.

  • SessionProvides access to the HttpSessionState object that maintains session state information. It can be used, for example, to track how many times a service is called while the current session is alive. This property is available only if the EnableSession property of the WebMethodAttribute is set to true.

  • ContextExposes the HttpContext class. Recall that this class provides a wealth of information about the current request.

  • UserReturns the IPrincipal object that provides user authentication information and can be used to determine whether the request is authorized (see Chapter 17).

To demonstrate the use of state information, let's extend our example to use the Application object to keep track of the number of times the Web Service is called.

Our first step is to add the statement in bold to the GetDayBorn method:

dob = dt.ToString("dddd"); // extracts day
this.Application["count"] = GetCount()+1;

Next, add a method to the class that internally returns the “count” value of the Application object. Finally, add a method, GetVisitors, which allows clients to view the number of calls to the Web Service as a Web Service call:

private int GetCount()
{
   object ob = this.Application["count"];
   if (ob == null) {
      return(0);
   } else {
      return((int)ob);
   }
}
[WebMethod(Description="Number of times Web Service called.")]
public int GetVisitors()
{
   return (GetCount());
}

Extending the Web Service with the WebService and WebMethod Attributes

The BirthDayWS example demonstrates that a functioning Web Service can be created with a minimal use of .NET classes. However, before releasing a Web Service to the Internet, there are additional features that should be considered. For instance, each XML Web Service should be given a unique namespace rather than relying on the default <http://tempuria.org>, caching can be implemented to improve performance, and overloaded methods can be implemented using the WebMethod attribute.

The WebService Attribute

The optional WebService attribute (not to be confused with the WebService directive) is applied to the class implementing the XML Web Service. It has two useful properties: Description, which describes the overall Web Service, and Namespace, which sets the default XML namespace for the service. In general terms, namespaces are used to avoid naming conflicts among XML elements and attributes. In this case, it is used to make the Web Service name unique.

Let's add a WebService attribute containing a namespace and description to the class in our BirthDayWS example:

[WebService(Namespace="http://www.dateservices.com/ws/",
   Description="<b>Web Service that Provides Date
   Functions.</b>")]
public class BirthDay : System.Web.Services.WebService

It is important to note that the namespace is intended to be a unique identifier, and does not need to actually point to anything. Domains are typically used to take advantage of their uniqueness.

Figure 18-5 shows the page that is returned when we call the Web Service that is now updated with the WebService attribute and the GetVisitors method.

Updated BirthDayWS Web Service

Figure 18-5. Updated BirthDayWS Web Service

The WebMethod Attribute

As mentioned previously, the WebMethod attribute is required to expose a method as part of a Web Service. Its properties include Description, which we have already used, EnableSession, MessageName, CacheDuration, and TransactionOption. The latter property applies to applications that call transactional COM+ components—a topic not discussed in this book. The other three properties are useful for developing general Web Services. Let's look at the role of each.

EnableSession: Activate the Use of Session State Information

This property is used with Web Services that inherit from the WebService class. Turning it on allows a Web Service method to access session information through WebService.Session or HttpContext.Current.Session. By default, this is set to false, because maintaining session data increases memory consumption and reduces performance.

Example: [WebMethod(EnableSession=true)]   // Default is false

Be aware that Windows Forms clients do not provide the same support for session state variables that a browser does. Sessions rely on cookies to preserve state data. Unlike browsers, Windows Forms applications do not store cookies. Thus, the service thinks that each request is the first request. There is a work-around, however, which is described in the following section on building a Web Services client.

MessageName: Create Overloaded Web Operations

Suppose we want to add a method to our Web Service that accepts the month as a string rather than an integer. Because the signature is different than the original GetDayBorn method, we can give it the same method name and C# will compile it as an overloaded method. However, when we try to access it from a browser, we receive a system exception indicating that the two methods have the same name.

The problem lies in the WSDL, which requires that each Web method in its XML elements be uniquely named, irrespective of its signature. By default, the routine in ASP.NET that generates WSDL code uses the name for both. The solution—aside from renaming the method—is to use the MessageName property to indicate a surrogate name.

[WebMethod(Description="Return day of week for any date",
       MessageName="GetDayBorn2")]
public string GetDayBorn(string month, int day, int yr){
   // Code to convert string month to int value
   string allMos= "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC";
   month = month.Substring(0,3).ToUpper();
   int ndx = allMos.IndexOf(month);
   if(ndx <0) err=true; else mo = ndx/3 +1;
   // Remainder of code goes here...

CacheDuration: Caching Output from a Web Operation

Just like ASP.NET Web pages, output from a Web method can be cached to obviate executing it each time the method is called. The CacheDuration property specifies how long (in seconds) the output is to be cached. For methods that accept arguments, cached output is saved for each unique set of arguments.

Example: [WebMethod(CacheDuration=1800)]   // Value is in seconds

The only rule to follow when (if) setting up caching is to do it judiciously. Caching greatly improves performance for methods that are requested frequently and implement logic that requires lengthy processing. On the other hand, methods such as GetDayBorn in our example can actually hurt performance because most requests to it will yield unique results.

Using web.config to Configure Web Service Options

.NET Web Services support four protocols: HttpGet, HttpPost, HttpSoap, and HttpLocalHost. Of these, HttpPost and HttpGet are disabled for security reasons. The effect of this is to limit Web Service access to URLs located on http://localhost; an attempt by a remote machine to access a local Web Service results in this message being displayed:

The test form is only available for requests from the local
machine

To make a Web Service available to remote users, it is necessary to enable the HttpPost and HttpGet protocols. This is easily done by configuring the <protocol> section of the web.config file to “add” the two protocols.

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

Recall that navigating to a Web Services page with no parameters brings up a help page that describes how to use the service, and provides links to invoke the Web Service methods or display the WSDL. If you do not want to expose this information, you can disable the display of help pages by removing the Documentation protocol. To do so, place the following statement in the <protocols> element of the web.config file:

<remove name="Documentation" />

Building an XML Web Service Client

This section describes how to create a client application that consumes a Web Service. The sobjective is to make the client's call to the remote Web method as simple as calling a method in its own code. Although the actual implementation does not reach quite that level of simplicity, the code is straightforward, and much of it can be generated automatically using .NET tools.

Before delving into details, let's first take a high-level view of how a .NET Web Services client interacts with a Web Service.

The most important thing to observe in Figure 18-6 is that the client does not directly invoke the Web Service method. Instead, it calls a proxy object that performs this task. The proxy class is created from the WSDL information provided by the Web Service. We'll see how to do this shortly. The proxy code is a class that has the same class name and method names as the Web Service class does. It contains the transport logic that allows it to make the actual connection to the Web Service. It may do this either synchronously (without receiving confirmation) or asynchronously. The messages exchanged between the proxy and server are bundled within an HTTP request and transmitted using either the HTTP or the SOAP wire protocol.

Overview of how a client accesses a Web Service

Figure 18-6. Overview of how a client accesses a Web Service

Creating a Simple Client to Access the Web Service Class

To demonstrate the basic principles involved in creating a Web Service client, let's develop a console client application to access the BirthDayWS Web Service (refer to Figure 18-1). The client passes three arguments to the service and prints the string it returns. Recall that the service consists of the Web class Birthday and a single method GetDayBorn:

public class BirthDay
{
   [System.Web.Services.WebMethod
        (Description="Return day of week for a date")]
public string GetDayBorn(int mo, int day, int yr)

Although the Web Service method can reside on a remote machine anywhere on the Internet, we can approach the client design as if we were within the same assembly. Here is the client code contained in the file bdClient.cs:

using System;
using System.Web.Services;
public class BirthDayClient
{
   static void Main(string[] args)
   {
      BirthDay bd = new BirthDay();
      string dayOfWeek = bd.GetDayBorn(12,20,1963);
      Console.WriteLine(dayOfWeek);
   }
}

Compiling this, of course, results in an error stating that BirthDay and bd cannot be found. We resolve this by creating a proxy class that performs the remote call, yet can be accessed locally by the client. The code for the proxy class is obtained by feeding the WSDL information that defines the Web Service into the .NET wsdl.exe utility.

Using wsdl.exe to Create a Proxy

The wsdl.exe utility reads the WSDL describing a Web Service and generates the source code for a proxy class to access the service; it can also use the information to create the skeleton code for a Web Service. This latter feature is designed for developers who prefer to design the WSDL as the first step in creating a Web Service. We do not take that approach in this chapter, but you should be aware that there are WSDL editors available for that task.

The wsdl.exe utility is run from the command line and has numerous flags or options to govern its execution. Table 18-1 lists those most commonly used.

Table 18-1. wsdl.exe Command-Line Options

Option

Description

/appsettingurlkey
/urlkey:

Specifies a key within the client's *.config file that contains the URL of the Web Service. The default is to hardcode the URL within the proxy class.

/language
/l:

Specifies the language to use for generating the proxy class. Choices are:

CS (C#), VB (Visual Basic), JS (JScript), and VJS (Visual J#).

C# is the default.

/namespace
/n:

Specifies the namespace for the proxy.

/out

Specifies file in which the generated proxy code is placed. The default is to use the XML Web Service name with an extension reflecting the language used for the code.

/protocol

The wire protocol to use within the proxy code:

/protocol:SOAP SOAP 1.1 is generated

/protocol:SOAP12 SOAP 1.2 is generated

/protocol:HttpGet or /protocol:HttpPost

/server

Generates an abstract class for the Web Service. This is used when the WSDL document is used to create a Web Service.

/serverinterface

Generates interfaces—rather than abstract classes—for the Web Service. Available only in 2.0 and later.

The following statement creates a proxy class from the BirthDayWS.asmx Web Service and places it in c:BDProxy.cs:

wsdl.exe /out:c:BDProxy.cs http://localhost/ws/
BirthDayWS.asmx?WSDL

This proxy source code can be used in two ways: include it in the client's source code, or compile it into a DLL and add a reference to this DLL when compiling the client code. Let's look first at the DLL approach. This command line compiles the source code and links two DLL files containing classes required by the proxy:

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

We are now ready to compile the client source code into an executable file, bdClient.exe:

csc /r:BDProxy.dll  bdClient.cs

If we add the proxy code directly to the bdClient.cs file and compile it, the result is a module that produces the same output as a version that links the proxy as a separate DLL file.

Core Note

Core Note

When wsdl.exe creates proxy code, it checks the associated Web Service for compliance with the WS-I Basic Profile 1.0 standards and emits a warning if the service is noncompliant. For information on this standard, refer to the Web Services Interoperability Organization's Web site at http://www.ws-i.org.

Listing 18-2 examines the proxy source code to understand how it converts a client's call into a remote Web Service invocation.

Example 18-2. Proxy Source Code to Access BirthDayWS.asmx Web Service

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
//
// Auto-generated by wsdl, Version=2.0.40607.16.
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(
       Name="BirthDaySoap",
       Namespace="http://tempuri.org/")]
public class BirthDay : SoapHttpClientProtocol {
   private System.Threading.SendOrPostCallback
         GetDayBornOperationCompleted;
   public BirthDay() {
         this.Url = "http://localhost/ws/BirthDayWS.asmx";
   }
   public event GetDayBornCompletedEventHandler
         GetDayBornCompleted;
   // (1) Synchronous Call to Web Service
   [SoapDocumentMethodAttribute(
      "http://tempuri.org/GetDayBorn",
      RequestNamespace="http://tempuri.org/",
      ResponseNamespace="http://tempuri.org/",
      Use=
      System.Web.Services.Description.SoapBindingUse.Literal,
      ParameterStyle=
      System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
   public string GetDayBorn(int mo, int day, int yr)
   {
      object[] results = this.Invoke("GetDayBorn", new object[] {
            mo,
            day,
            yr});
      return ((string)(results[0]));
   }
   // (2) Asynchronous call to Web Service
   public System.IAsyncResult BeginGetDayBorn(int mo, int day,
         int yr, System.AsyncCallback callback,
         object asyncState) {
      return this.BeginInvoke("GetDayBorn", new object[] {
            mo,
            day,
            yr}, callback, asyncState);
   }

   public string EndGetDayBorn(System.IAsyncResult asyncResult) {
         object[] results = this.EndInvoke(asyncResult);
         return ((string)(results[0]));
   }
   // (3) Call this for event-based asynchronous handling
   public void GetDayBornAsync(int mo, int day, int yr) {
         this.GetDayBornAsync(mo, day, yr, null);
   }

   public void GetDayBornAsync(int mo, int day, int yr,
         object userState) {
      if ((this.GetDayBornOperationCompleted == null)) {
            this.GetDayBornOperationCompleted = new
            System.Threading.SendOrPostCallback(
               this.OnGetDayBornOperationCompleted);
      }
      this.InvokeAsync("GetDayBorn", new object[] {
            mo,
            day,
            yr}, this.GetDayBornOperationCompleted,
            userState);
   }

   private void OnGetDayBornOperationCompleted(object arg) {
      if ((this.GetDayBornCompleted != null)) {
            InvokeCompletedEventArgs invokeArgs =
               ((InvokeCompletedEventArgs)(arg));
            this.GetDayBornCompleted(this, new
               GetDayBornCompletedEventArgs(invokeArgs.Results,
               invokeArgs.Error, invokeArgs.Cancelled,
               invokeArgs.UserState));
      }
   }
   public new void CancelAsync(object userState) {
         base.CancelAsync(userState);
   }
}
public delegate void GetDayBornCompletedEventHandler(
      object sender, GetDayBornCompletedEventArgs args);
public class
      GetDayBornCompletedEventArgs :AsyncCompletedEventArgs
{
   private object[] results;
   internal GetDayBornCompletedEventArgs(object[] results,
      System.Exception exception, bool cancelled,
      object userState) : base(exception, cancelled, userState) {
         this.results = results;
   }
   // Results are available as a property
   public string Result {
     get {
           this.RaiseExceptionIfNecessary();
           return ((string)(this.results[0]));
      }
   }
}

Observe that the proxy class has the same name as the Web Service class (Birthday) it represents, and implements a method (GetDayBorn) having the same name as the Web Service method. The supporting code is quite different, however. It contains transport logic rather than application logic. This code uses methods provided by the System.Web.Services.Protocols.SoapHttpClientProtocol class from which the proxy class derives. Table 18-2 summarizes of the more useful members of the class.

Table 18-2. Selected Members of SoapHttpClientProtocol

Member

Description

BeginInvoke()

Begins an asynchronous invocation of the Web method.

EndInvoke()

Ends an asynchronous invocation of the Web method.

Invoke()

Invokes the Web method synchronously.

CookieContainer

Gets or sets the collection of cookies. This permits a proxy-based client to accept cookies and allow a server to maintain state information.

Proxy

Gets or sets proxy information needed to make a Web Service call through a firewall.

Timeout

Gets or sets the timeout (in milliseconds) a client waits for a synchronous call to a Web Service to be completed.

Url

Gets or sets the URL used to access the Web server.

Let's look at how these members are used in a proxy class.

Synchronous Calls to a Web Service Method

A proxy provides the capability to invoke a Web Service method synchronously or asynchronously. Recall from Chapter 13, “Asynchronous Programming and Multithreading,” that a synchronous operation uses only one thread, which is blocked until control returns from the called method. An asynchronous call, on the other hand, creates a worker thread that handles the operations of the called method. Control returns immediately to the main thread while a separate thread handles the auxiliary task. The calling routine is notified when the task is completed.

Synchronous communication is implemented with a proxy method that has the same name as the Web method and uses the Invoke method to request the service.

public string GetDayBorn(int mo, int day, int yr) {
     object[] results = this.Invoke("GetDayBorn", new object[] {
           mo, day, yr});
     return ((string)(results[0]));
}

This is the most intuitive way to access a service because a client can be totally unaware of the proxy and execute a call to the Web Service method, using its actual name. However, because a synchronous call by definition does not require a response, the client code should include a timeout interval and handle any exception that occurs if the interval is exceeded. The following code sets a timeout of four seconds and handles any System.Net.WebException that is thrown.

// Must add using System.Net;
BirthDay bd = new BirthDay();
bd.Timeout= 4000;         // Set timeout to 4 seconds
try
{
   string dayOfWeek = bd.GetDayBorn(12,20,1963);
   Console.WriteLine(dayOfWeek);
}
catch (WebException ex)
{
   Console.WriteLine(ex.Message);  // Will report timeout
}
catch (Exception ex)
{
   Console.WriteLine("Unknown error occurred.");
}

Core Note

Core Note

An easy way to test the timeout exception is to add a Thread.Sleep() call within the Web method being called on the Web server. This suspends thread execution for a specified amount of time. For example:

System.Threading.Thread.Sleep(3000);  // Sleep 3 seconds

Asynchronous Calls to a Web Service Method

Asynchronous communication is performed using the Beginmethodname and Endmethodname methods supplied by the proxy. Internally, these call the BeginInvoke and EndInvoke methods, respectively. BeginInvoke queues the method to be run on a separate (worker) thread. It returns an IAsyncResult type that is subsequently passed to the EndInvoke method to retrieve the results. As shown in the following code, the IAsyncResult type is also used to determine if the call has been completed.

BirthDay bd = new BirthDay();
// Pass null callback argument since we will poll status
IAsyncResult dayOfWeek =bd.BeginGetDayBorn(12,20,1963,null,null);
// Perform other operations here while waiting for response
If (dayOfWeek.IsCompleted)
      Console.Write(bd.EndGetDayBorn(dayOfWeek));

This “polling” approach can be used to periodically check the status of the request. Another approach is to have the Web Service call a method in the client code when the response is ready. The AsyncCallback delegate is used to specify the method that receives the callback.

// Create delegate that specifies method to be called back
AsyncCallback wscb = new AsyncCallback(DayReturned);
BirthDay bd = new BirthDay();
IAsyncResult dayOfWeek =
      bd.BeginGetDayBorn(12,20,1963,wscb,null);
// This method is called back when the web method completes
public static void DayReturned(IAsyncResult result)
{
      BirthDay bd = new BirthDay();
      Console.WriteLine(bd.EndGetDayBorn(result));
}

As with polling, the method implementing EndInvoke (EndGetDayBorn, in this example) is called to return the result.

Event-Based Asynchronous Calls

If you take a close look at the proxy code in Listing 18-2, you'll notice that it includes a delegate and corresponding event declaration:

public event GetDayBornCompletedEventHandler
      GetDayBornCompleted;

public delegate void GetDayBornCompletedEventHandler(
      object sender, GetDayBornCompletedEventArgs args);

These enable client code to treat the Web Service invocation as an event that can be handled by a local event handler. Only a few lines of code are required to hook up an event handler to the event defined in the proxy:

Birthday service = new Birthday();
// (1) Associate event handler with event
service.GetDayBornCompleted += new
      GetDayBornCompletedEventHandler(this.ShowOutput);
// (2) Invoke service asynchronously
service.GetDayBornAsync();
// (3) Event handler called when service returns value
private void ShowOutput(object sender,
      GetDayBornCompletedEventArgs args)
{
   Console.WriteLine(args.Result);
}

The Web Service is invoked asynchronously by calling the proxy provided method GetDayBornAsync (<web service name>Async). When the Web Service finishes execution, .NET returns the results to the event handler through the args parameter.

Using the CookieContainer Property to Maintain Session State

Setting a Web Service method's EnableSession attribute to true enables the method to maintain state information. However, this is only effective if the client can accept cookies. This is not usually a problem with browsers, but Windows Forms (WinForms) applications do not store cookies by default. The CookieContainer property is a way to permit WinForms clients to accept cookies.

To permit the client to accept cookies, set the CookieContainer property of the proxy class instance to a CookieContainer object. The code shown here permits the Web Service to maintain state information over all queries made by this client.

// BirthDay bd = new BirthDay() .. must have class level scope
if (bd.CookieContainer == null)
   bd.CookieContainer = new CookieContainer();
IAsyncResult dayOfWeek =  bd.BeginGetDay-
Born(mo,dy,yr,null,null);
Console.WriteLine(bd.EndGetDayBorn(dayOfWeek));
// List all cookies in CookieContainer collection
// Create a Uniform Resource Identifier object
Uri hostURL = new Uri("http://localhost");
foreach (Cookie ck in bd.CookieContainer.GetCookies(hostURL))
Console.WriteLine(ck.Name+" "+ck.TimeStamp.ToString());

Notice that the CookieContainer class has a method GetCookies that returns a collection of cookies stored in the container. In the example, foreach is used to enumerate the collection and list the name of each cookie along with the date it was created.

Creating a Proxy with Visual Studio.NET

We have shown how the proxy code can be generated using wsdl.exe and compiled into a DLL or used as source code within the client application. If you use VS.NET to create your Web Service client, you have no need for the wsdl.exe utility. Instead, VS.NET lets you select a Web Service for which you want to generate the proxy code. It adds the code to the project, and you can use the techniques described in the preceding examples to access it. Here are the details:

  1. Open up .NET and select a Windows application.

  2. Select Add Web Reference under the Project menu tab. The subsequent screen contains a text box at the top in which you enter the URL of the Web Service. For example:

    http://localhost/ws/BirthDayWS.asmx
    
  3. Press the Enter key and a screen appears that includes a Web Service Help page for the selected service. Click the Add Reference button and the source code containing the proxy is added to the project.

  4. The proxy code has its own namespace that should be added to the client code. In this example, wsdlclient is the name of the VS.NET project.

    using wsdlclient.localhost;
    

You will see that a Web References folder has been added to the directory of the project. To view the proxy source, click the Show All Files buttons in the Solution Explorer and open the Reference.cs file beneath the localhost node. Notice that in addition to the proxy code, the directory includes the WSDL file from which the proxy was generated. We'll look at this file in detail in the next section to gain an understanding of how WSDL describes a Web Service. In addition, we will look at how SOAP is used to define the format of messages transported between the Web Service consumer and provider.

Understanding WSDL and SOAP

The purpose of this section is to add some depth to the topic of Web Services—to look underneath at the XML grammar that is used to describe the service (WSDL) and the protocol (SOAP) used for transporting data between the service and client. Be aware that both of these have been submitted to the World Wide Web Consortium (W3C), but neither is an official standard.

Web Services Description Language (WSDL)

WSDL is broadly defined as “an XML format for describing network services as a set of endpoints operating on messages containing either document-oriented or procedure-oriented information.”[2] In our case, the endpoints are the client and a Web Service, and WSDL defines how the client interacts with the service.

When developing a Web Service from the ground up, it is good practice to develop the interface definition first—in the form of WSDL—and then map it to the implementation code. Although the sheer complexity of WSDL demands that WSDL editors be used for the task, there is still a need for the developer to have a general understanding of the WSDL structure. The same is true even if you work only on the client side and use wsdl.exe to create source code directly from the WSDL. Problems can arise using utilities and editors that require a familiarity with the format in order to perform troubleshooting. For example, a well-designed WSDL interface is often built from multiple documents tied together by an XML import element. Being familiar with the semantics and existence of the import element goes a long way toward solving import issues—a not uncommon source of WSDL errors.

This section introduces the basic structure of WSDL, which should satisfy the curiosity of the occasional Web Service consumer or developer. Those interested in learning more should refer to the specification that is published by W3C at http://www.w3.org/TR/wsdl.

The WSDL Structure

Specifications for the WSDL grammar define six major elements: definitions, types, message, port type, binding, and service. Let's discuss these within the context of the WSDL file that describes the sample BirthDayWS Web Service.

<Definitions>

This is the root element of the WSDL document. It declares multiple namespaces used throughout the document, and contains all of the other elements:

<definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:s="http://www.w3.org/2001/XMLSchema"
   xmlns:s0="http://tempuri.org/"
   xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
   xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
   xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
   targetNamespace="http://tempuri.org/"
   xmlns="http://schemas.xmlsoap.org/wsdl/">

Namespaces are used to distinguish elements because it is possible that elements from different namespaces could have the same name.

<Types>

This element contains an XSD (XML Schema Definition Language) schema that describes the data types publicly exposed by the service: the parameters passed in the Web Service request, and the response:

<types>
   <s:schema elementFormDefault="qualified"
       targetNamespace="http://tempuri.org/">
   <s:element name="GetDayBorn">
   <s:complexType>
   <s:sequence>
   <s:element minOccurs="1" maxOccurs="1" name="mo"
      type="s:int" />
   <s:element minOccurs="1" maxOccurs="1" name="day"
      type="s:int" />
   <s:element minOccurs="1" maxOccurs="1" name="yr"
      type="s:int" />
   </s:sequence>
   </s:complexType>
   </s:element>
   <s:element name="GetDayBornResponse">
   <s:complexType>
   <s:sequence>
   <s:element minOccurs="0" maxOccurs="1" name="GetDayBornResult"
      type="s:string" />
   </s:sequence>
   </s:complexType>
   </s:element>
   </s:schema>
</types>

<Message>

Defines the data that is exchanged between the Web Service provider and consumer. Each message is assigned a unique name and defines its parameters—if any—in terms of names provided by the types element:

<message name="GetDayBornSoapIn">
   <part name="parameters" element="s0:GetDayBorn" />
   </message>
   <message name="GetDayBornSoapOut">
   <part name="parameters" element="s0:GetDayBornResponse" />
   </message>

<PortType>

Each <portType> element defines the <Message> elements that belong to a communications transport. The name attribute specifies the name for the transport. The <portType> element contains <operation> elements that correspond to the methods in the Web Service. The <input> and <output> elements define the messages associated with the operation. Four types of operations are supported: one-way, in which the service receives a message; request-response, in which the client sends a request; solicit-response, in which the service first sends a message to the client; and notification, where the service sends a message to clients.

<portType name="BirthDaySoap">
   <operation name="GetDayBorn">
   <documentation>Return day of week for any date</documentation>
   <input message="s0:GetDayBornSoapIn" />
   <output message="s0:GetDayBornSoapOut" />
   </operation>
</portType>

<Binding>

A set of rules that describe how the <portType> operation is transmitted over the wire. Wire protocols available are HTTP GET, HTTP POST, and SOAP. This example demonstrates how SOAP is specified.

As acknowledgement of the importance of SOAP as a transport protocol, the WSDL 1.1 specification includes extensions for SOAP 1.1. These extension elements include <binding>, <operation>, and <body>.

<binding name="BirthDaySoap" type="s0:BirthDaySoap">
   <soap:binding transport=
      "http://schemas.xmlsoap.org/soap/http"
      style="document" />
   <operation name="GetDayBorn">
      <soap:operation soapAction="http://tempuri.org/GetDayBorn"
         style="document" />
      <input>
         <soap:body use="literal" />
      </input>
      <output>
         <soap:body use="literal" />
      </output>
   </operation>
</binding>

Note that the <operation> element specifies the entry point for the Web method that is called on the server. One other thing to be aware of is the style attribute in the binding element. This value, which may be document or rpc, specifies how an operation is formatted. By default, .NET sets this value to document. To specify rpc, you must apply the SoapRpcMethodAttribute to the Web method:

[SoapRpcMethod][WebMethod]
public string GetDayBorn(string month, int day, int yr)

Although there is a rather spirited debate among WSDL purists as to which is better, you can safely ignore the histrionics and use the .NET default. However, knowing your options will enable you to easily work with third parties that may have a preference.

<Service>

Identifies the location of the Web Service. Specifically, it lists the name of the Web Service class, the URL, and references the binding for this endpoint.

<service name="BirthDay">
   <port name="BirthDaySoap" binding="s0:BirthDaySoap">
      <soap:address location=
      "http://localhost/ws/BirthDayWs.asmx" />
   </port>
</service>

Simple Object Access Protocol (SOAP)

SOAP is a platform-neutral protocol for exchanging information. Its cross-platform capabilities are attributable to its use of XML to define the data being passed and support for HTTP as a communications protocol. SOAP is the most popular and flexible protocol for the exchange of information between Web Service consumers and providers. Its format allows it to define complex data structures not supported by the competing HTTP GET and POST protocols.

Our discussion of SOAP follows the same approach used with WSDL: We examine the basic features of SOAP using the request/response messages generated from the BirthDayWS Web Service example. The format of these messages is described on the Web page containing the desired method(s) of the Web Service.

A SOAP Request Message

The header for a SOAP request reveals that the SOAP request is packaged as an HTTP POST request to the server designated by the Host field. The length field specifies the number of characters in the body of the POST, and SOAPAction indicates the namespace and method to be contacted.

POST /ws/BirthDayWS.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/GetDayBorn"

Listing 18-3 shows the XML template for the SOAP message that is sent to the server.

Example 18-3. GetDayBorn SOAP Request Content

<?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
GetDayBorn SOAP Request Content/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http:/
GetDayBorn SOAP Request Content/tempuri.org/" xmlns:types="http://tempuri.org/encodedTypes" xmlns:soap="http://schemas
GetDayBorn SOAP Request Content.xmlsoap.org/soap/envelope/">
   <soap:Body
      soap:encodingStyle=
         "http://schemas.xmlsoap.org/soap/encoding/">
      <tns:GetDayBorn>
         <mo xsi:type="xsd:int">int</mo>
         <day xsi:type="xsd:int">int</day>
         <yr xsi:type="xsd:int">int</yr>
      </tns:GetDayBorn>
   </soap:Body>
</soap:Envelope>

The overall structure of a SOAP message is not complex. It is an XML document that has a mandatory root element, <Envelope>, an optional <Header> element, and a mandatory <Body>.

A SOAP envelope, as the name implies, is conceptually a container for the message. The SOAP header represents a way to extend the basic message. It may contain additional information about the message and—as we will see later—can be used to add a measure of security. The SOAP body contains what one would regard as the actual data: the arguments sent to the service and the response. The contents of the <Body> element in this example consist of the method name and its three parameters that correspond to the call made within the client code:

string dayOfWeek = bd.GetDayBorn(12,20,1963);

A SOAP Response Message

The SOAP body of the response includes a <GetDayBornResult> element (see Listing 18-4) that contains the response from the Web Service and identifies it as a string type.

Example 18-4. GetDayBorn SOAP Response Content

<?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-
enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/" xmlns:types="http://tem-
puri.org/encodedTypes" xmlns:soap="http://schemas.xml-
soap.org/soap/envelope/">
   <soap:Body
      soap:encodingStyle=
         "http://schemas.xmlsoap.org/soap/encoding/">
      <tns:GetDayBornResponse>
         <GetDayBornResult
            xsi:type="xsd:string">string</GetDayBornResult>
      </tns:GetDayBornResponse>
   </soap:Body>
</soap:Envelope>

Using the SOAP Header for User Authentication

The optional SOAP header is available for adding miscellaneous information about its associated SOAP message. One popular use for this header is to include identification information about the user making the request. This enables user authentication to be performed by the methods within the Web Service.

Core Note

Core Note

A question that arises when a Web Service is being invoked from a Web page is whether Forms Authentication (Chapter 17) can be used. The short answer is yes, but it requires a special implementation. Unlike a regular Web page, a Web Service does not recognize the authentication cookie created by the separate login screen. The solution is to add a login method to the Web Services that creates the authentication cookie. In addition, each service must check user authentication before performing its operation. The coding requirements are comparable to using SOAP header authentication.

Listing 18-5 demonstrates a Web Service that checks the header for an ID and password before making the service available to the requestor. It consists of the code taken from Listing 18-1 and updated to include features required to access a SOAP header.

Example 18-5. Authenticate Web Service User by Checking SOAP Header

using System;
using System.Web.Services;
// Required for SoapException
using System.Web.Services.Protocols;
namespace BirthDayWS
{
// (1) Class to hold data passed in the header
   public class SOAPHeaderContent : SoapHeader
   {
      public string UserName;
      public string PassWord;
   }
   public class BirthDay : System.Web.Services.WebService
   {
      // (2) Member class accessing header data
      public SOAPHeaderContent headerInfo;
      // (3) Add SoapHeader attribute
      [WebMethod(Description="Return day of week for any date"),
            SoapHeader("headerInfo")]
      public string GetDayBorn(int mo, int day, int yr)
      {
         if (!Verify())
         {
            throw new SoapException(
                  "Valid User info not included.",
                  SoapException.ClientFaultCode);
         } else {
            bool err = false;
            string dob;
            if (mo <1 || mo >12) err=true;
            if (day < 1 || day > 31) err=true;
            if ( ! err)
            {
               DateTime dt = new DateTime(yr,mo,day);
               dob = dt.ToString("dddd"); // extracts day
            }  else {
               dob = "Invalid Date";
            }
            return(dob);
         }
      }
      // Method to check password and ID in SOAP header
      private bool Verify()
      {
         // (4) Access data in header
         if (headerInfo.UserName != "Vincent" ||
             headerInfo.PassWord != "arles")
         { return(false);
         } else { return(true);}
      }
   }
}

This example illustrates the four steps that are followed to receive and access any SOAP header data:

  1. Create a class to represent the data in the header. This class must inherit from the SoapHeader class. SOAPHeaderContent serves the purpose in this code.

  2. Add a member to the Web Service class that is the same type as the class created in Step 1. The example uses headerInfo.

  3. Apply a SoapHeader attribute that references the member created in Step 2. Applying this to a method makes the information in the header available to the method.

  4. Process the header data by accessing the members of the class created in Step 1. The Verify method contains the simple logic to check PassWord and UserName. In reality, the comparison information would come from a database rather than being hardcoded.

The proxy for this Web Service includes the class that represents the header contents. The client creates an instance of this class and assigns a password and user name. The class instance is assigned to a field that is now a member of the proxy class representing the Web Service class. In this example, Birthday now has a field named SOAPHeaderContentValue. As you have probably guessed, .NET creates this field name by appending Value to the name of the class that accesses the header info (SOAPHeader).

using System;
using System.Web.Services;
using system.Web.Services.Protocols;
using System.Text.RegularExpressions;
public class BirthDayClient
{
   static void Main(string[] args)
   {
      SOAPHeaderContent acctInfo = new SOAPHeaderContent();
      acctInfo.UserName = "Vincent";
      acctInfo.PassWord = "arles";
      BirthDay bd = new BirthDay();
      bd.SOAPHeaderContentValue = acctInfo;
      try {
         string dayOfWeek = bd.GetDayBorn(12,20,1963);
         Console.WriteLine(dayOfWeek);
      } catch (SoapException ex)
         {
            // Extract Soap error message
            // Be sure to add:
            // using System.Text.RegularExperssions
            Match errMatch = Regex.Match(ex.Message,":(.*)");
            Console.WriteLine(errMatch.Groups[1]);
         }
   }
}

Handling SOAP Exceptions

As shown in Listing 18-5, the Web Service throws an exception if the user name and password cannot be authenticated:

throw new SoapException("Valid User info not included.",
      SoapException.ClientFaultCode);

The exception is rendered as a SoapException object. In this example, its constructor receives a message and a SOAP fault code that signifies the type of error. This information is returned to the client as a <Fault> element in the SOAP body and is converted by the .NET Framework back to a SoapException object that can be processed by the client's code.

The SoapException object includes four properties that provide information about the exception:

  • MessageThe error message that explains the reason for the exception.

  • ActorThe URL of the Web Service that threw the exception.

  • CodeAn XMLQualifiedName object that specifies one of four SOAP fault codes that categorize the exception. These fault codes are represented as static fields of the SoapException class.

  • DetailAn XMLNode object containing application-specific information about the error.

The Message and Code properties are used most frequently in processing a SOAP exception. The message is verbose: It consists of the full namespace qualification of the SoapException class followed by the actual message and the name of the Web method where the exception occurred. A regex was used in the preceding client code to extract the actual message.

The SoapException class contains static fields that can be compared with the Code value to broadly classify the exception. These fields include the following:

  • VersionMismatchFaultCodeThe SOAP envelope has an invalid namespace.

  • ClientFaultCodeThe message sent by the client is incorrectly formatted or contains incorrect information.

  • ServerFaultCodeThe error occurs on the server and is not related to the SOAP message. An example would be a network or hardware problem.

  • MustUnderstandFaultCodeA SOAP element marked with the MustUnderstand attribute set to true is not processed. This is an attribute that indicates the particular element must be processed.

Here is a code sample that demonstrates using the Code property. It first extracts a message embedded in the Message property and prints it. Then it checks the Code property to classify the excepton.

try {
      string dayOfWeek = bd.GetDayBorn(12,20,1963);
      Console.WriteLine(dayOfWeek);
    }
catch (SoapException ex)
    {
       Match errMatch = Regex.Match(ex.Message,":(.*)");
       Console.WriteLine(errMatch.Groups[1]);
       // Check various fault codes here
       if(ex.Code == SoapException.ClientFaultCode)
       {
          Console.WriteLine("Problem with Client message.");
       }
       if(ex.Code == SoapException.ServerFaultCode)
       {
          Console.WriteLine("Problem with Server. Try again.");
       }
    }

SOAP Security

The preceding example illustrates a lightweight technique for using SOAP headers to authenticate a user. Its main drawback is that the contents of the SOAP headers are passed as cleartext. To overcome this, you can add a layer of encryption by using this technique in conjunction with Secure Sockets Layer (SSL).

As an alternative to this and other ad hoc approaches to SOAP security, there is now a Web Services Security (WS-Security) specification [3] that defines enhancements to SOAP messaging. Its objectives are to ensure the following:

  • Authentication. How a SOAP message expresses the identity of its sender.

  • Integrity. How to verify that a SOAP message has not been tampered with.

  • Confidentiality. What protects the contents of a SOAP message from being read by an intermediary.

In support of this specification, Microsoft provides an add-on to the .NET Framework known as Web Services Enhancements 2.0 (WSE 2.0). This tool set comprises a .NET class library that can be used to integrate the WS-Security specification into Web Service applications. Note that the use of these enhancements requires .NET on both the client and service provider endpoints, so it's not yet a generic Web Service security solution. See the MSDN Web site, msdn.microsoft.com/webservices/webservices/building/wse/default.aspx, for whitepapers and a free download of the enhancements library

Using Web Services with Complex Data Types

The BirthDayWS Web Service used throughout this chapter accepts integers as input and returns a string value. This is useful for introducing Web Service principles because HTTP GET, HTTP POST, and SOAP all support it. However, Web Services also have the capability of serving up more complex data types such as data sets, hash tables, images, and custom objects.

Before data can be sent to or from a Web Service, it is serialized using XML serialization. Conversely, it is deserialized on the receiving end so it can be restored to its original type. As we saw in Chapter 4, “Working with Objects in C#,” not all data can be serialized. Thus, when designing a Web Service, it is important to understand restrictions that apply to serialization:

  • XML serialization can only be used with classes that contain a public parameterless constructor. For example, you may have a Web Service that returns a hash table because it has the constructor public Hashtable(). On the other hand, the Bitmap class does not have a parameterless constructor and cannot be used as a return type.

  • Read-only properties in a class cannot be serialized. The property must have a get and set accessor and be public.

  • Fields must be public to be serialized; private ones are ignored.

In this section, we work with two Web Service examples that illustrate the use of complex data. Our first example creates a Web Service that accepts the name of an image and returns it as a byte stream. The second example creates a client to use the Amazon Web Services provided by Amazon.com, Inc. These services offer a rich—but practical—sampling of accessing multiple Web methods and processing a wide variety of custom classes.

A Web Service to Return Images

Image manipulation usually requires representing an image as a Bitmap object. However, because bitmaps cannot be serialized and transferred directly, we must find an indirect way to transport an image. The not-so-difficult solution is to break the image into bytes and return a byte stream to the client, who is responsible for transforming the stream to an image.

The logic on the server side is straightforward: a FileStream is opened and associated with the image file. Its contents are read into memory and converted to a byte array using

tempStream.ToArray()

This byte array is then sent to the Web client (see Listing 18-6).

Example 18-6. Web Service to Return an Image as a String of Bytes

<%@ WebService Language="C#" Class="WSImages" %>
using System;
using System.Web.Services;
using System.IO;
using System.Web.Services.Protocols;
public class WSImages: System.Web.Services.WebService {
   [WebMethod(Description="Request an Image")]
   public byte[] GetImage(string imgName) {
      byte[] imgArray;
      imgArray = getBinaryFile("c:\"+imgName+".gif");
      if (imgArray.Length <2)
      {
         throw new SoapException(
           "Could not open image on server.",
            SoapException.ServerFaultCode);
      } else
      {
         return(imgArray);
      }
   }
   public byte[] getBinaryFile(string filename)
   {
      if(File.Exists(filename)) {
         try {
            FileStream s = File.OpenRead(filename);
            return ConvertStreamToByteBuffer(s);
         }
         catch(Exception e)
         {
            return new byte[0];
         }
      } else { return new byte[0]; }
   }
   // Write image to memory as a stream of bytes
   public byte[] ConvertStreamToByteBuffer(Stream imgStream) {
      int imgByte;
      MemoryStream tempStream = new MemoryStream();
      while((imgByte=imgStream.ReadByte())!=-1) {
         tempStream.WriteByte(((byte)imgByte));
      }
      return tempStream.ToArray();  // Convert to array of bytes
   }
}

Our client code receives the byte stream representing an image and reassembles it into a Bitmap object. Because the Bitmap constructor accepts a stream type, we convert the byte array to a MemoryStream and pass it to the constructor. It can now be manipulated as an image.

WSImages myImage = new WSImages();
try {
   // Request an image from the Web Service
   byte[] image = myImage.GetImage("stanwyck");
   MemoryStream memStream = new MemoryStream(image);
   Console.WriteLine(memStream.Length);
   // Convert memory stream to a Bitmap
   Bitmap bm = new Bitmap(memStream);
   // Save image returned to local disk
   bm.Save("c:\bstanwyck.jpg",
        System.Drawing.Imaging.ImageFormat.Jpeg);
}
catch (WebException ex)
{
   Console.WriteLine(ex.Message);
}

Using Amazon Web Services

To use the Amazon E-Commerce Service, you must register for a developer's token, which is required as part of all requests made to the Web Services. In addition, you should download (http://www.amazon.com/webservices) the developer's kit that contains the latest documentation, examples, and—most importantly—a WSDL file defining all the services.

An examination of the WSDL file reveals that AmazonSearchService is the Web Service class that contains the numerous search methods available to clients. These methods provide the ability to search the Amazon product database by keyword, author, artist, ISBN number, manufacturer, actor, and a number of other criteria. Each search method takes a search object as an argument that describes the request to the server and returns a ProductInfo object. For example, a request to search by keywords looks like this:

AmazonSearchService amazon = new AmazonSearchService();
KeywordRequest kwRequest = new KeywordRequest();
// Set fields for kwRequest
ProductInfo products = amazon.KeywordSearchRequest(kwRequest);

Sending a Request with the AmazonSearchService Class

Table 18-3 contains a sampling of the methods available for searching Amazon products. These methods are for accessing the Web Service synchronously. An asynchronous form of each method is also available that can be accessed using the techniques discussed earlier in this chapter.

Table 18-3. Selected Methods of AmazonSearchService Class

Method

Description

ProductInfo KeyWordSearchRequest
 (KeywordRequest req)

Method to return items that contain one or more keywords provided in request.

ProductInfo AsinSearchRequest
 (AsinRequest req)

Method to return a book having a requested Amazon Standard Identification Number (ASIN) that is the same as the book's ISBN. Represented as a 10-digit string.

ProductInfo AuthorSearchRequest
(AuthorRequest req)

Method to return names of all books by requested author.

ProductInfo ActorSearchRequest
(ActorRequest req)

Method to return video titles of movies in which a specified actor or actress was a cast member.

ProductInfo PowerSearchRequest
(PowerRequest req)

Method to retrieve book information based on a Boolean query that may include a combination of title, subject, author, keyword, ISBN, publisher, language, and publication date (pubdate).

Each call to a Web method passes an object that describes the search request. This object is different for each method—for example, AuthorSearchRequest requires an AuthorRequest object, whereas KeyWordSearchRequest requires an instance of the KeywordRequest class. These classes expose almost identical fields. Each contains a unique string field that represents the search query, five other required fields common to each class, and some optional fields for sorting or specifying a locale. Table 18-4 lists unique and shared fields for each method listed in Table 18-3.

Table 18-4. Selected Fields for Classes That Define a Search Request

Field

Description

KeywordRequest.Keyword
AsinRequest.Asin
ActorRequest.Actor
AuthorRequest.Author
PowerRequest.Power

These are string values containing the search value or query. For example:

PowerRequest pr = new PowerRequest();
   pr.Power = "author:Nabokov and
   keyword:butterfly";

string page

Page of results to display.

string mode

Type of products being searched—for example, "books".

string tag

Amazon associate's ID. Use "webservices-20" as default.

string type

"lite" or "heavy". Determines how much XML data is returned.

string devtag

Developer's token assigned to you by Amazon.

Using the ProductInfo Class to Process the Web Service Response

The Web Service responds to the search request with a ProductInfo object containing results from the search. This object exposes three important fields: a TotalResults string contains the number of products retrieved by the request, a TotalPages string that indicates how many pages these results are displayed in, and the important Details array that contains a detailed description of products that constitute one returned page of results. This array is of the Details type. Table 18-5 shows the fields that are related to books.

Table 18-5. Selected Fields of the Details Class

Field

Description

string ProductName

Name of a single product.

string SalesRank

Ranking of product based on sales of items of its type.

string Publisher

Publisher of book.

String ListPrice
string OurPrice

List and sales price of book.

Reviews[] Reviews

The Reviews class contains several fields relating to

reviews of the book:
string AvgCustomerRating
string TotalCustomerReviews
CustomerReview CustomerReviews
string Comment
string Rating

String[] Authors

One or more authors for the book.

This is only a small sample of the fields available in the Details class. There is a particularly rich set of fields worth exploring that define video products.

Creating a Proxy for the Amazon Web Services

Our first step is to create a proxy class from the Amazon WSDL information. The downloadable kit includes a WSDL file, and it can also be retrieved from the Internet, as we do here. Using the VS.NET command line, we place the proxy source code in the file AZProxy.cs.

wsdl.exe  /out:c:clientAZProxy.cs
      http://soap.amazon.com/schema3/AmazonWebServices.wsdl

Next, we create an assembly, AZProxy.dll, containing the proxy that will be used by client code. It is linked to assemblies containing .NET Web classes required by the application.

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

You can make a quick test of the service using this barebones application, azclient.cs:

using System;
using System.Web.Services;
namespace webclient.example {
public class AmazonClient
{
   static void Main(string[] args)
   {
      // Search for books matching keyword "butterflies"
      AmazonSearchService amazon = new AmazonSearchService();
      KeywordRequest kwRequest = new KeywordRequest();
      kwRequest.keyword = "butterflies";
      kwRequest.type = "heavy";
      kwRequest.devtag= "*************"; // your developer token
      kwRequest.mode = "books";       // search books only
      kwRequest.tag = "webservices-20";
      kwRequest.page = "1";          // return first page
      ProductInfo products =
          amazon.KeywordSearchRequest(kwRequest);
      Console.WriteLine(products.TotalResults);  // Results count
   }
}
}

Compile and execute this from the command line:

csc /r:AZProxy.dll azclient.cs
azclient

When azclient.exe is executed, it should print the number of matching results.

Building a WinForms Web Service Client

Let's design a Windows Forms application that permits a user to perform searches on books using multiple search options. Open VS.NET and select a Windows application. After this is open, we need to add a reference to the proxy assembly AZProxy.dll. From the menu, select Project – Add Reference – Browse. Click the assembly when it is located and then click OK to add it as a reference. You also need to add a reference to System.Web.Services.dll, which contains the required Web Service namespaces.

The purpose of the application is to permit a user to search the Amazon book database by keyword, author, or title. The search can be on a single field on a combination of fields. Figure 18-7 shows the interface for entering the search values and viewing the results. The Search buttons submit a search request based on the value in their corresponding text box. The Power Search button creates a query that logically “ands” any values in the text boxes and submits it.

Overview of how a client accesses a Web Service

Figure 18-7. Overview of how a client accesses a Web Service

A single page of results is displayed in a ListView control. Beneath the control are buttons that can be used to navigate backward and forward through the results pages.

Each Search button has a Click event handler that calls a method to create an appropriate request object and send it to the Amazon Web Service. A successful call returns a ProductInfo object containing information about up to 10 books meeting the search criteria. Listing 18-7 displays code that creates an AuthorRequest object, sends it to the Web Service, and calls FillListView to display the results in the ListView control.

Example 18-7. Client Code to Display Results of Author Search—azwsclient.cs

// Fields having class-wide scope
int CurrPg;               // Current page being displayed
string SearchMode = "";   // Current search mode
int MaxPages =1;          // Number of pages available
// This method is called when author Search button is clicked
private bool AuthorReq()
{
   AmazonSearchService amazon = new AmazonSearchService();
   AuthorRequest auRequest = new AuthorRequest();
   auRequest.author = textBox2.Text;   // Get author from GUI
   auRequest.type = "heavy";
   auRequest.devtag= "****KLMJFLGV9";  // Developer token
   auRequest.mode = "books";
   auRequest.tag = "webservices-20";
   auRequest.page = CurrPg.ToString();
   try
   {
      // Call Web Service with author query
      ProductInfo products =
            amazon.AuthorSearchRequest(auRequest);
      FillListView(products);
      return(true);
   }
   catch (SoapException ex)
   {
      MessageBox.Show(ex.Message);
      return(false);
   }
}
private void FillListView(ProductInfo products)
   listView1.Items.Clear();   // Remove current entries
   label6.Text="";            // Clear any title
   label4.Text = products.TotalResults;
   label5.Text = CurrPg.ToString()+" of "+products.TotalPages;
{
   MaxPages =  Convert.ToInt32(products.TotalPages);
   ListViewItem rowItem;
   string auth,rev;
   for (int i=0; i< products.Details.Length; i++)
   {
      rowItem = new
            ListViewItem(products.Details[i].ProductName);
      // Add Author. Make sure author exists.
      object ob = products.Details[i].Authors;
      if (ob != null)  auth =
            products.Details[i].Authors[0]; else auth="None";
      rowItem.SubItems.Add(auth);
      // Add Price
      rowItem.SubItems.Add(products.Details[i].OurPrice);
      // Add Average Rating
      ob = products.Details[i].Reviews;
      if (ob != null)  rev =
            products.Details[i].Reviews.AvgCustomerRating;
            else rev="None";
      rowItem.SubItems.Add(rev);
      // Add Date Published
      rowItem.SubItems.Add(
            products.Details[i].ReleaseDate);
      listView1.Items.Add(rowItem);
   }
}

The keyword, title, and power searches use an identical approach: Each has a routine comparable to AuthorReq that creates its own request object. The only significant difference pertains to the power search that creates a Boolean query from the search field values. The format for this type query is field:value AND field2:value AND field3:value. For example:

"author:hemingway AND keywords:Kilimanjaro"

This application was designed as a Windows Forms application. It could just as easily be set up as Web page under ASP.NET. The code to reference the assembly AZProxy.dll is identical. The ListView control is not supported on a Web Form, but you could easily substitute a DataGrid for it.

Web Services Performance

The performance of a Web Service from both the client and server side is affected by a variety of factors. Some are .NET related and others are inherent in the technology. The solutions for improving Web Services performance range from shaving a few milliseconds off the way .NET sends a request to simply eschewing Web Services for an alternate protocol when transferring large amounts of data. We'll look at all of these.

Configuring the HTTP Connection

Connections (HTTP) to Internet resources are managed in .NET by the ServicePoint class. This class includes properties to specify a connection's timeout interval, set its security protocol, and manage the use of server security certificates. It also includes properties that directly affect how much delay is incurred before a Web Service request is transmitted over a network: UseNagleAlgorithm and Expect100Continue. Despite the awkward sounding names, setting their properties is as easy as assigning a true or false value to them. The default for both is true.

Expect100Continue

This property determines whether a POST request should expect to receive a 100-Continue response from the server to indicate that the data can be posted. If this value is set to true, only the request header portion of a request is sent to the server. If the server finds no problem with the header, it returns a 100-Continue response, and the data is then sent. Two trips are required. If the property is set to false, the initial request includes the headers and data. If it is rejected by the server, the data has been sent unnecessarily; if it is accepted, a second trip is not necessary.

Because Web Service calls tend to pass small amounts of data, it can be beneficial to turn this property off. Even if a request is rejected, only a small amount of data will have been sent.

The Nagle Algorithm

One way to improve network efficiency is to reduce the number of small data packets sent across a network. To accomplish this, the software layer controlling the underlying TCP (Transmission Control Protocol) connection attempts to accumulate, or buffer, small messages into a larger TCP segment before they are sent. The technique to do this is based on the Nagle algorithm.[4]

The crux of the algorithm is that small amounts of data should continue to be collected by TCP until it receives acknowledgment to send the data. .NET institutes a delay of up to 200 milliseconds to collect additional data for a packet. For a typically small Web Service request, there may be no reason to include this delay. It's an option you can experiment with.

To set the Expect100Continue and UseNagleAlgorithm properties, it is necessary to get a reference to the ServicePoint object being used to handle the Web request. This is done in the proxy code on the client side. Refer to Listing 18-2, and you'll see that the proxy code consists of a class derived from the base SoapHttpClientProtocol class. By overriding the inherited GetWebRequest method, you can customize the WebRequest object before the request is sent to the Web Service.

Add the following code to override the GetWebRequest method. Inside the method, you use the Uri object to get the ServicePoint. Its properties can then be turned off or on to test performance:

// Using System.Net must be added
protected override WebRequest GetWebRequest( Uri uri)
{
   // Locate ServicePoint object used by this application
   ServicePoint sp =
         ServicePointManager.FindServicePoint(uri);
   sp.Expect100Continue = false;
   sp.UseNagleAlgorithm = false;
   return WebRequest.Create(uri);
}

Working with Large Amounts of Data

Although the XML format offers a great deal of flexibility in representing data, it can place a potentially large burden on a network because of the large files that can be generated. Moreover, processing XML data can require extensive memory resources on the client and server. Finally, there is the nature of a Web Service: If the transmission fails at any point, the entire response must be resent. This is in contrast to FTP and the HTTP GET verb that allow partial data transmission.

For large amounts of data, consider these options:

  • Use FTP or a Web client as described in Section 17.6, “Creating a Web Client with WebRequest and WebResponse.”

  • Avoid calls as much as possible by caching data rather re-requesting it.

  • Look at compression techniques such as HTTP transport compression or the SOAP extensions that can compress part of a Web Service message.

  • Wait for new Web Service standards. Of particular note is Message Transmission Optimization Mechanism (MTOM), a new W3C recommendation—pushed by Microsoft—that details a method for attaching large binary data to a SOAP message.

Summary

.NET supports the development of both Web Service provider and Web Service consumer applications. On the server side, a .NET Web Service is easily constructed using a WebService directive to define the Web Service class and a WebMethod attribute to identify methods accessible to HTTP requests. For the Web Service to be used by clients, a description of the service(s) must be made available. This is the purpose of the Web Service Description Language (WSDL) contract. This XML file describes the service, the methods available, and a description of the arguments each method accepts. You can generate the contract by using a browser to navigate to a URL address that consists of the URL of the Web Service with ?WSDL appended to it.

The WSDL information is used on the client side to create a Proxy class that is used to actually communicate with the Web Service. This proxy is created using the wsdl.exe utility or within a Visual Studio.NET project as part of the Add Web Service option. The proxy defines methods that permit the Web Service methods to be accessed synchronously or asynchronously. The latter technique returns control to the application while the request is being processed.

SOAP (Simple Object Access Protocol) describes the XML format that is used to transport information between a Web Service provider and consumer. Its structure consists of an envelope, optional header, and body. The body contains the actual data or message; the header may contain annotation about the message. One such use is to include user authentication information. The main advantage of SOAP over the other two wire protocols—HTTP GET and HTTP POST—is that it supports the transmission of non-text data such as images and objects. We demonstrated this by building a client to access the Amazon Web Services.

Test Your Understanding

1:

Which base class must the Web Service class inherit from for the Web Service to use the ASP.NET Session and Application objects?

2:

What is the difference between a WebService attribute and a WebService directive?

3:

How can we create a Web Service that exposes two methods having the same name?

4:

Which SoapHttpClientProtocol member is used to make a synchronous call to a Web method? An asynchronous call?

5:

You are using a Web Service that has heavy traffic or is subject to downtime. How can you ensure that your client program waits no longer than 10 seconds for a request?

6:

Which WSDL element contains the URL of the target Web Service?

  1. <Definitions>
    
  2. <Message>
    
  3. <Service>
    
  4. <Binding>
    

7:

Which attribute must be applied to a Web method for the method to access a SOAP header?

8:

What three ways can a proxy be used to make asynchronous calls to a Web Service?



[1] Simple Object Access Protocol (SOAP) 1.1—W3C Note, May 8, 2000.

[2] Web Services Descriptive Language (WSDL) 1.1—W3C Note, March 15, 2002.

[4] RFC 896, “Congestion Control in IP/TCP Internetworks,” by John Nagle, 1984.

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

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