Web Services

A great place to begin an exploration of Web services is to define precisely what a Web service is. A Web service is an application that:

Most Web services expect their Web methods to be invoked using HTTP requests containing SOAP messages. SOAP is an XML-based vocabulary for performing remote procedure calls using HTTP and other protocols. You can read all about it at http://www.w3.org/TR/SOAP. Suppose you write a Web service that publishes Web methods named Add and Subtract that callers can use to add and subtract simple integers. If the service’s URL is www.wintellect.com/calc.asmx, here’s how a client would invoke the Add method by transmitting a SOAP envelope in an HTTP request. This example adds 2 and 2:

POST?/calc.asmx?HTTP/1.1
Host:?www.wintellect.com
Content-Type:?text/xml;?charset=utf-8
Content-Length:?338
SOAPAction: "http://tempuri.org/Add"

<?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>
????<Add?xmlns="http://tempuri.org/">
??????<a>2</a>
??????<b>2</b>
????</Add>
??</soap:Body>
</soap:Envelope>

And here’s how the Web service would respond:

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

<?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>
????<AddResponse?xmlns="http://tempuri.org/">
??????<AddResult>4</AddResult>
????</AddResponse>
??</soap:Body>
</soap:Envelope>

The Web service’s job is to parse the SOAP envelope containing the inputs, add 2 and 2, formulate a SOAP envelope containing the sum of 2 and 2, and return it to the client in the body of the HTTP response. This, at the most elemental level, is what Web services are all about.

Web services written with the .NET Framework also allow their Web methods to be invoked using ordinary HTTP GET and POST commands. The following GET command adds 2 and 2 by invoking the Web service’s Add method:

GET?/calc.asmx/Add?a=2&b=2?HTTP/1.1
Host:?www.wintellect.com

The Web service responds as follows:

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

<?xml?version="1.0" encoding="utf-8"?>
<int?xmlns="http://tempuri.org/">4</int>

Here’s a POST command that adds 2 and 2:

POST?/calc.asmx/Add?HTTP/1.1
Host:?www.wintellect.com
Content-Type:?application/x-www-form-urlencoded
Content-Length:?7

a=2&b=2

And here’s the Web service’s response:

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

<?xml?version="1.0" encoding="utf-8"?>
<int?xmlns="http://tempuri.org/">4</int>

As you can imagine, the hard part of writing a Web service is parsing HTTP requests and generating HTTP responses. But as you’ll see in the next section and throughout the remainder of this chapter, the .NET Framework insulates developers from the low-level details of HTTP, SOAP, and XML and provides a high-level framework for writing Web services and Web service clients alike.

There are many ways to write Web services. You can write Web services by hand. You can use SOAP toolkits from Microsoft, IBM, and other companies. And you can use the .NET Framework. Because this book is about Microsoft .NET, this chapter is about the latter. Writing Web services with the .NET Framework offers two advantages over all the other methods:

What does it take to write a Web service using the .NET Framework? I’m glad you asked, because that’s what the next section is about.

Your First Web Service

The ASMX file shown in Figure 11-1 is a complete Web service. It implements two Web methods: Add and Subtract. Both take two integers as input and return an integer as well. Deploying the Web service is as simple as copying it to a directory on your Web server that is URL-addressable. If you put Calc.asmx in wwwroot, the Web service’s local URL is http://localhost/calc.asmx.

Calc.asmx demonstrates several important principles of Web service programming using the .NET Framework:

Despite its brevity, Calc.asmx is a full-blown Web service when installed on a Web server outfitted with ASP.NET. Its Web methods can be invoked with SOAP, HTTP GET, and HTTP POST, and it’s capable of returning output in SOAP responses or simple XML wrappers. All we need now is a way to test it out. The .NET Framework lends a hand there too.

Testing a Web Service

How do you test an ASMX Web service? Simple: just call it up in your browser. To demonstrate, copy Calc.asmx to wwwroot and type

http://localhost/calc.asmx

in your browser’s address bar. You’ll be greeted with the screen shown in Figure 11-2. What happened? ASP.NET responded to the HTTP request for Calc.asmx by generating an HTML page that describes the Web service. The name and description in the ASMX file’s WebService attribute appear at the top of the page. Underneath is a list of Web methods that the service exposes, complete with the descriptions spelled out in the WebMethod attributes.

Figure 11-2
Calc.asmx as seen in Internet Explorer.

Like what you’ve seen so far? It gets better. Click “Add” near the top of the page, and ASP.NET displays a page that you can use to test the Add method (Figure 11-3). ASP.NET knows the method name and signature because it reads them from the metadata in the DLL it compiled from Calc.asmx. It even generates an HTML form that you can use to call the Add method with your choice of inputs. Type 2 and 2 into the “a” and “b” boxes and click Invoke. The XML returned by the Web method appears in a separate browser window (Figure 11-4).

Figure 11-3
Test page for the Add method.
Figure 11-4
XML returned by the Add method.

The forms that ASP.NET generates on the fly from ASMX files enable you to test the Web services that you write without writing special clients to test them with. They also let you explore a Web service built with the .NET Framework simply by pointing your browser to it. For kicks, type the following URL into your browser’s address bar:

http://terraservice.net/terraservice.asmx

That’s the URL of the Microsoft TerraService, an ultra-cool Web service that provides a programmatic interface to a massive database of geographic data known as the Microsoft TerraServer. Don’t worry about the details just yet; you’ll be using TerraService to build a Web service client later in this chapter. But do notice how much you can learn about TerraService simply by viewing the page that ASP.NET generated for it.

Web Services and Code-Behind

You can use code-behind to move Web service classes out of ASMX files and into separately compiled DLLs. Figure 11-5 shows how Calc.asmx looks after it’s modified to take advantage of code-behind. The ASMX file now contains just one statement. The class referenced in that statement is implemented in Calc.cs. The following command compiles Calc.cs into a DLL named Calc.dll:

csc?/t:library?calc.cs

Once compiled, the DLL must be placed in the application root’s bin subdirectory (for example, wwwroot\bin).

Calc.asmx
<%@?WebService?Class="CalcService" %>
Calc.cs
using?System;
using?System.Web.Services;

[WebService?(Name="Calculator?Web?Service",
????Description="Performs?simple?math?over?the?Web")]
class?CalcService
{
????[WebMethod?(Description="Computes?the?sum?of?two?integers")]
????public?int?Add?(int?a,?int?b)
????{
????????return?a?+?b;
????}

????[WebMethod
????????(Description="Computes?the?difference?between?two?integers")]
????public?int?Subtract?(int?a,?int?b)
????{
????????return?a?-?b;
????}
}
Figure 11-5
Calc Web service with code-behind.

Code-behind offers the same benefits to Web services that it offers to Web pages: it catches compilation errors before the service is deployed, and it enables you to write Web services in languages that ASP.NET doesn’t natively support. For an example of ASP.NET code written in managed C++ and for a refresher on code-behind in general, turn back to Chapter 5.

The WebService Base Class

Very often when you see ASMX code samples, the Web service classes inside them derive from a class named WebService, as in

class?CalcService?:?WebService
{
??...
}

WebService belongs to the System.Web.Services namespace. It contributes properties named Application, Session, Context, Server, and User to derived classes, enabling a Web service to access the ASP.NET objects with the same names. If you don’t use these objects in your Web service—for example, if you don’t use application state or session state—you don’t need to derive from WebService either.

The WebMethod Attribute

The WebMethod attribute tags a method as a Web method. The .NET Framework automatically exposes such methods as Web methods when they’re implemented inside a Web service. WebMethod is capable of doing much more, however, than simply letting the framework know which methods are Web methods and which are not; it also supports the following parameters:

Parameter Name

Description

BufferResponse

Enables and disables response buffering

CacheDuration

Caches responses generated by this method for the specified number of seconds

Description

Adds a textual description to a Web method

EnableSession

Enables and disables session state for this Web method

MessageName

Specifies the Web method’s name

TransactionOption

Specifies the transactional behavior of a Web method

CacheDuration is the ASMX equivalent of an @ OutputCache directive in an ASPX or ASCX file: it caches a method’s output so that subsequent requests will execute more quickly. Suppose, for example, that you write a Web method that returns the current time:

[WebMethod]
public?string?GetCurrentTime?()
{
????return?DateTime.Now.ToShortTimeString?();
}

Since ToShortTimeString returns a string that includes minutes but not seconds, it’s wasteful to execute it too often. The following method declaration uses CacheDuration to cache the output for 10 seconds at a time:

[WebMethod?(CacheDuration="10")]
public?string?GetCurrentTime?()
{
????return?DateTime.Now.ToShortTimeString?();
}

Now the data that the method returns could be stale by a few seconds, but if the Web service is getting pounded with calls to GetCurrentTime, the load on it will be reduced commensurately.

Web services enjoy access to the same session state facilities that conventional ASP.NET applications do. By default, however, session state is disabled for Web methods. You can enable it with WebMethod’s EnableSession parameter. If you want to use session state in a Web service, derive from WebService (to inherit its Session property) and tag each Web method that uses session state with EnableSession=“true”:

class?CalcService?:?WebService
{
????[WebMethod?(EnableSession="true",
????????Description="Adds?an?item?to?a?shopping?cart")]
????public?void?AddToCart?(Item?item)
????{
????????ShoppingCart?cart?=?(ShoppingCart)?Session["MyShoppingCart"];
????????cart.Add?(item);
????}
}

Session state utilization is less common in Web services than in conventional Web applications, but it is an option nonetheless.

The MessageName parameter lets you assign a Web method a name other than that of the method that implements it. For example, suppose that you build two Add methods into a Web service—one that adds integers and another that adds floating point values—and you tag both of them as Web methods:

[WebMethod]
public?int?Add?(int?a,?int?b)
{
????return?a?+?b;
}

[WebMethod]
public?float?Add?(float?a,?float?b)
{
????return?a?+?b;
}

The only problem with this code is that it doesn’t compile. C# methods can be overloaded, but Web methods cannot. The solution? Either change the method names or add MessageName parameters to the WebMethod attributes, as demonstrated here:

[WebMethod?(MessageName="AddInts")]
public?int?Add?(int?a,?int?b)
{
????return?a?+?b;
}

[WebMethod?(MessageName="AddFloats")]
public?float?Add?(float?a,?float?b)
{
????return?a?+?b;
}

Now the C# methods remain overloaded, but the corresponding Web methods are named AddInts and AddFloats.

The Web Services Description Language

If other developers are to consume (that is, write clients for) a Web service that you author, they need to know what Web methods your service publishes, what protocols it supports, the signatures of its methods, and the Web service’s location (URL), among other things. All this information and more can be expressed in a language called the Web Services Description Language, or WSDL for short.

WSDL is a relatively new standard. It’s an XML vocabulary devised by IBM, Microsoft, and others. Its syntax is documented at http://www.w3.org/TR/wsdl. I won’t describe the details of the language here for several reasons. First, the details are already documented in the spec. Second, WSDL is a language for machines, not humans. Third, it’s trivial to generate a WSDL contract for a Web service built with the .NET Framework: simply point your browser to the Web service’s URL and append a WSDL query string, as in

http://www.wintellect.com/calc.asmx?wsdl

Figure 11-6 shows the result. Scan through it and you’ll find a service element that describes the Web service; operation elements that document the “operations,” or Web methods, that the service supports; binding elements that document the protocols that the Web methods support; and other descriptive information.

Figure 11-6
WSDL contract for Calc.asmx.

When you publish a Web service, you should also publish a WSDL contract describing it. For a Web service built with the .NET Framework, the contract is usually nothing more than a URL with ?wsdl on the end. Other developers can use the contract to write clients for your Web service. Typically, they don’t read the contract themselves. Instead, they run it through a tool that generates a wrapper class containing all the elements needed to talk to a Web service. The .NET Framework SDK includes one such tool: it’s called Wsdl.exe. You’ll learn all about it later in this chapter when we turn our attention from Web services to Web service clients.

Web Services and Complex Data Types

It’s not hard to understand how simple data types can be passed to and from Web methods. After all, integers and other primitive types are defined in one form or another on virtually every platform. But what about more complex types? What if, for example, you define a custom class or struct and want to use it as an input parameter or return value for a Web method? Are complex types supported, and if so, how do you declare them so that they become an intrinsic part of the Web service?

Complex types are supported, and they work very well because virtually any type can be represented in XML. As an example, consider the Web service in Figure 11-7. It exposes a Web method named FindStores that accepts a state abbreviation (for example, “CA”) as input. FindStores calls a local method named FindByState, which queries the Pubs database that comes with Microsoft SQL Server for all the bookstores in the specified state and returns the results in an array of Bookstore objects. (Observe that FindByState is not a Web method because it lacks a WebMethod attribute.) FindStores returns the array to the client. Bookstore is a custom type defined in the ASMX file.

Figure 11-8 shows the XML returned when FindStores is called with the input string “CA”. The array of Bookstore objects has been serialized into XML. The serialization is performed by the .NET Framework’s System.Xml.Serialization.XmlSerializer class, otherwise known as the “XML serializer.” A client application that receives the XML and that has a schema describing the structure and content of the data can rehydrate the information into Bookstore objects. Or it can take the raw XML and do with it as it pleases.

Locator.asmx
<%@?WebService?Language="C#" Class="LocatorService" %>

using?System;
using?System.Web.Services;
using?System.Data;
using?System.Data.SqlClient;

[WebService?(Name="Bookstore?Locator?Service",
????Description="Retrieves?bookstore?information?from?the?Pubs?database")]
class?LocatorService
{
????[WebMethod?(Description="Finds?bookstores?in?a?specified?state")]
????public?Bookstore[]?FindStores?(string?state)
????{
????????return?FindByState?(state);
????}

????Bookstore[]?FindByState?(string?state)
????{
????????SqlDataAdapter?adapter?=?new?SqlDataAdapter
????????????("select?*?from?stores?where?state?=?\'" +?state?+ "\'",
??????????? "server=localhost;database=pubs;uid=sa;pwd=");
????????DataSet?ds?=?new?DataSet?();
????????adapter.Fill?(ds);

????????DataTable?table?=?ds.Tables[0];
????????Bookstore[]?stores?=?new?Bookstore[table.Rows.Count];

????????for?(int?i=0;?i<table.Rows.Count;?i++)?{
????????????stores[i]?=?new?Bookstore?(
????????????????table.Rows[i]["stor_name"].ToString?().TrimEnd
????????????????????(new?char[]?{?'?'?}),
????????????????table.Rows[i]["stor_address"].ToString?().TrimEnd
????????????????????(new?char[]?{?'?'?}),
????????????????table.Rows[i]["city"].ToString?().TrimEnd
????????????????????(new?char[]?{?'?'?}),
????????????????table.Rows[i]["state"].ToString?().TrimEnd
????????????????????(new?char[]?{?'?'?})
????????????);
????????}
????????return?stores;
????}
}

public?class?Bookstore
{
????public?string?Name;
????public?string?Address;
????public?string?City;
????public?string?State;

????public?Bookstore?()?{}

????public?Bookstore?(string?name,?string?address,?string?city,
????????string?state)
????{
????????Name?=?name;
????????Address?=?address;
????????City?=?city;
????????State?=?state;
????}
}
Figure 11-7

Bookstore locator Web service.
Figure 11-8
XML returned by the FindStores method.

Where might a client obtain an XML schema describing the Bookstore data type? From the service’s WSDL contract, of course. Sneak a peek at Locator.asmx’s WSDL contract and you’ll see the Bookstore data type (and arrays of Bookstores) defined this way in the contract’s types element:

<s:complexType?name="ArrayOfBookstore">
??<s:sequence>
????<s:element?minOccurs="0" maxOccurs="unbounded"
??????name="Bookstore" nillable="true" type="s0:Bookstore" />?
??</s:sequence>
</s:complexType>
<s:complexType?name="Bookstore">
??<s:sequence>
????<s:element?minOccurs="1" maxOccurs="1" name="Name"
??????nillable="true" type="s:string" />?
????<s:element?minOccurs="1" maxOccurs="1" name="Address"
??????nillable="true" type="s:string" />?
????<s:element?minOccurs="1" maxOccurs="1" name="City"
??????nillable="true" type="s:string" />?
????<s:element?minOccurs="1" maxOccurs="1" name="State"
??????nillable="true" type="s:string" />?
??</s:sequence>
</s:complexType>

Given these definitions, a client can define a Bookstore class of its own and initialize arrays of Bookstore objects by deserializing Bookstore elements. It’s not as hard as it sounds. If the client is written with the .NET Framework, tools generate the class definitions for you and the framework handles the deserialization.

As Locator.asmx demonstrates, it’s not difficult to write Web methods that use custom types. There are, however, two gotchas to be aware of:

Keep these caveats in mind and you’ll have few problems combining Web methods and custom data types.

Web Service Discovery—DISCO

Once a client has a WSDL contract describing a Web service, it has all the information it needs to make calls to that Web service. But when you publish a Web service by making it available on a Web server, how do clients find out where to get a WSDL contract? For that matter, how do clients know that your Web service exists in the first place?

The answer comes in two parts: DISCO and Universal Description, Discovery, and Integration, better known as UDDI. The former is a file-based mechanism for local Web service discovery—that is, for getting a list of available Web services from DISCO files deployed on Web servers. The latter is a global Web service directory that is itself implemented as a Web service. UDDI is discussed in the next section.

The DISCO (short for “discovery”) protocol is a simple one that revolves around XML-based DISCO files. The basic idea is that you publish a DISCO file on your Web server that describes the Web services available on it and perhaps on other servers as well. Clients can interrogate the DISCO file to find out what Web services are available and where the services’ WSDL contracts can be found. As an example, suppose you publish two Web services and their URLs are as follows:

To advertise these Web services, you can deploy the following DISCO file at a well-known URL on your server. The contractRef elements identify the URLs of the Web services’ WSDL contracts. URLs can be absolute or relative (relative to the directory in which the DISCO file resides). The optional docRef attributes identify the locations of documents describing the Web services, which, because of the self-documenting nature of Web services built with the .NET Framework, are typically the ASMX files themselves:

<?xml?version="1.0" ?>
<discovery?xmlns="http://schemas.xmlsoap.org/disco/"
??xmlns:scl="http://schemas.xmlsoap.org/disco/scl/">
??<scl:contractRef?ref="http://www.wintellect.com/calc.asmx?wsdl"
????docRef="http://www.wintellect.com/Calc.asmx" />
??<scl:contractRef?ref="http://www.wintellect.com/locator.asmx?wsdl"
????docRef="http://www.wintellect.com/Locator.asmx" />
</discovery>

If you’d prefer, you can write DISCO files for individual Web services and reference them in a master DISCO file using discoveryRef elements. Here’s a DISCO file that points to other DISCO files. Once more, URLs can be absolute or relative:

<?xml?version="1.0" ?>
<discovery?xmlns="http://schemas.xmlsoap.org/disco/">
??<discoveryRef?ref="http://www.wintellect.com/calc.disco" />
??<discoveryRef?ref="http://www.wintellect.com/locator.disco" />
</discovery>

A third option is to deploy a VSDISCO file to enable dynamic discovery. The following VSDISCO file automatically exposes all ASMX and DISCO files in a host directory and its subdirectories, with the exception of those subdirectories noted with exclude elements:

<?xml?version="1.0" ?>
<dynamicDiscovery
??xmlns="urn:schemas-dynamicdiscovery:disco.2000-03-17">
??<exclude?path="_vti_cnf" />
??<exclude?path="_vti_pvt" />
??<exclude?path="_vti_log" />
??<exclude?path="_vti_script" />
??<exclude?path="_vti_txt" />
</dynamicDiscovery>

How does dynamic discovery work? ASP.NET maps the file name extension .vsdisco to an HTTP handler that scans the host directory and subdirectories for ASMX and DISCO files and returns a dynamically generated DISCO document. A client that requests a VSDISCO file gets back what appears to be a static DISCO document.

For security reasons, Microsoft disabled dynamic discovery just before version 1.0 of the .NET Framework shipped. You can reenable it by uncommenting the line in the httpHandlers section of Machine.config that maps *.vsdisco to System.Web.Services.Discovery.DiscoveryRequestHandler and granting the ASPNET account permission to access the IIS metabase. Microsoft highly discourages dynamic discovery for fear of compromising your Web server, and a bug in version 1.0 of the .NET Framework SDK prevents most DISCO-aware tools from working with VSDISCO anyway. My advice is to forget that VSDISCO files even exist and use static DISCO files instead.

To further simplify Web service discovery, you can link to a master DISCO file from your site’s default HTML document. For example, suppose the default HTML document at www.wintellect.com is Default.html and that the same directory also holds a discovery document named Default.disco. Including the following HTML in Default.html enables most tools that read DISCO files to accept the URL www.wintellect.com (as opposed to www.wintellect.com/default.disco):

<html>
??<head>
????<link?type="text/html" rel="alternate" href="Default.disco">
??</head>
</html>

Visual Studio .NET (specifically, its Add Web Reference command) reads DISCO files; so does the Disco.exe utility that comes with the .NET Framework SDK.

DISCO’s chief disadvantage is that you can’t read a DISCO file if you don’t have its URL. So how do you find a Web service if you don’t even have a URL to start with? Can you spell U-D-D-I?

Web Service Discovery—UDDI

UDDI is an abbreviation for Universal Description, Discovery, and Integration. Jointly developed by IBM, Microsoft, and Ariba and supported by hundreds of other companies, UDDI is a specification for building distributed databases that enable interested parties to “discover” each other’s Web services. No one company owns the databases; anyone is free to publish a UDDI-based business registry. Operator sites have already been established by IBM and Microsoft and are likely to be the first of many such sites that will come on line in the future.

UDDI sites are themselves Web services. They publish a pair of SOAP-based APIs: an inquiry API for inquiring about companies and their Web services and a publisher API for advertising a company’s Web services. Anyone can call the inquiry API, but operator sites typically limit the publisher API to registered members. Documentation regarding these APIs and other information about UDDI can be found at http://www.uddi.org. At the time of this writing, Microsoft was beta testing a UDDI .NET SDK featuring managed wrapper classes that simplify interactions with UDDI business registries. For the latest information on this and other UDDI development tools proffered by Microsoft, visit http://uddi.microsoft.com/default.aspx.

Most developers will never deal with UDDI APIs directly. Instead, they’ll use high-level tools such as Visual Studio .NET to query UDDI business registries and generate wrapper classes that allow them to place calls to the Web services that they find there. The actual placing of UDDI calls will be limited primarily to tools vendors and to clients that wish to locate and bind to Web services dynamically.