Normal web pages allow interaction between the client browser and the web server hosting the web page. Many businesses, however, need to provide information not to users, but to other programs.
For example, Amazon.com has a service that allows applications to send in the ISBN of a book and get back information about the publisher, sale price, sales rank, and so forth. This service has no user interface: it is a way for your program to interact with their data. Such a service, providing information with no user interface, using standard web protocols, is called a web service.
Unlike previous technologies for distributed computing (such as DCOM), web services make it unnecessary for both ends of the connection to be running the same operating system or to be programmed in the same language. For example, the server code might be written in VB.2005 on Windows 2000 while the client is a C++ program running on a Unix machine, or vice versa. In other words, while previous technologies required that the client and server be tightly coupled, web services permit them to be loosely coupled.
All that is necessary is that both server and client support the industry standard protocols HTTP, SOAP, and XML. HTTP is the protocol used by the Web. SOAP (Simple Object Access Protocol) is a lightweight, object-oriented protocol based on XML that, in turn, is a cross-platform standard for formatting and organizing information.
Microsoft has used web services to mimic RPC (remote procedure calls). You ask for an “object” from a web service, and what you get back provides the public interface to an object running on the web service’s server. You can interact with that object, calling methods and examining or setting properties. In this model, web services allow an object on the server to expose program logic to clients over the Internet. Clients call exposed methods on the web service using standard Internet protocols.
Typically, both ends of the connection communicate using SOAP messages (see the sidebar "SOAP“), which consist of self-describing, text-based XML documents.[*]
There are two broad aspects to web service development: creating the web service and consuming the web service. You’ll start by creating a simple web service that provides a simulated stock information service. Your web service will expose two methods:
Function GetName(stockSymbol As String) as String Function GetPrice (stockSymbol As String) as Float
If this web service were an actual production program, the data returned would be fetched from a live database. In order not to confuse web service issues with data access issues, the data in this chapter will be stored in a two-dimensional array of strings.
Begin by creating a new web site named StockPriceWebService
. Be sure to click on ASP.NET Web Service in the templates window, as shown in Figure 14-1.
Visual Studio 2005 does a lot of the work of setting up your web service, including adding the necessary <WebService>
attributes to your class, and creating a template method (HelloWorld
) complete with <WebMethod>
attributes. For more on attributes, see the note on attributes in Chapter 12.
You’ll add a two dimensional array of stock symbols with their names and fictional prices, as shown in Example 14-1.
Dim stocks As String(,) = _ { _ {"MSFT", "Microsoft", "25.22"}, _ {"DELL", "Dell Computers", "42.12"}, _ {"INTC", "Intel", "25.50"}, _ {"YHOO", "Yahoo!", "30.81"}, _ {"GE", "General Electric", "37.51"}, _ {"IBM", "International Business Machine", "91.98"}, _ {"GM", "General Motors", "64.72"}, _ {"F", "Ford Motor Company", "25.05"} _ }
You are now ready to create your two web methods. Web methods are exposed to web clients by tagging them with the <WebMethod>
attribute. The first method, GetPrice
, takes a symbol as a string and returns the price, as shown in Example 14-2.
<WebMethod()> _ Public Function GetPrice(ByVal StockSymbol As String) As Double Dim returnValue As Double = 0 For counter As Integer = 0 To stocks.GetLength(0) − 1 If (String.Compare(StockSymbol, stocks(counter, 0), True) = 0) Then returnValue = Convert.ToDouble(stocks(counter, 2)) End If Next Return returnValue End Function
You can imagine a two-dimensional array as an array of arrays. The first value passed into the offset operator for stocks (counter)
, ticks through each of the internal arrays in turn:
stocks(counter
, 0)
The second value passed in, (offset 0), picks from within the internal array. The first field (offset 0) is the stock symbol, the second symbol (offset 1) is the stock name, and the third field (offset 2) is the price:
returnValue = Convert.ToDouble(stocks(counter, 2))
The GetName
method is extremely similar, except that instead of returning a price, it returns the name of the stock, as shown in Example 14-3.
<WebMethod()> _ Public Function GetName(ByVal StockSymbol As String) As String Dim returnValue As String = "Symbol not found." For counter As Integer = 0 To stocks.GetLength(0) − 1 If (String.Compare(StockSymbol, stocks(counter, 0), True) = 0) Then returnValue = stocks(counter, 1) End If Next Return returnValue End Function
Notice that Visual Studio 2005 created an .asmx page, but it has only one line in it:
<%@ WebService Language="vb" CodeBehind="~/App_Code/Service.vb" Class="Service" %>
This determines that the code for the web service will be in the code behind (Service.vb) file. The Class
attribute created by Visual Studio 2005 ties this .asmx page to the class defined in Service.vb.
Your new class derives from WebService
. While this is the most common way to create a web service, it is optional. However, doing so does give you access to a variety of useful ASP.NET objects: Application
and Session
(for preserving state); User
(for authenticating the caller); and Context
(for access to HTTP-specific information about the caller’s request, accessed through the HttpContext
class).
Each web method is preceded by the WebMethod
attribute. You are free to add properties to this attribute. The following sections describe the valid WebMethod
properties.
By default, ASP.NET buffers the entire response to a request before sending it from the server to the client. Under most circumstances, this is the optimal behavior. However, if the response is very lengthy, you might want to disable this buffering by setting the WebMethod
attribute’s BufferResponse
property to False
. If set to False
, the response will be returned to the client in 16 KB chunks.
<WebMethod(BufferResponse:=False)>
Web services, like web pages, can cache the results returned to clients. If a client makes a request that is identical to a request made recently by another client, then the server will return the response stored in the cache. This can result in a huge performance gain, especially if servicing the request is an expensive operation (such as querying a database or performing a lengthy computation).
For the cached results to be used, the new request must be identical to the previous request. If the web method has parameters, the parameter values must also be identical. For example, if the GetPrice
web method of the StockTicker
web service is called with a value of msft
passed in as the stock symbol, that result will be cached separately from a request for dell
.
The CacheDuration
property defines how long the response is cached, in seconds. Once the CacheDuration
period has expired, a new page is sent. To set the CacheDuration
for 30 seconds, you’d write,
<WebMethod(CacheDuration:=30)>
The default value for CacheDuration
is 0
, which disables caching of results.
The WebMethod
attribute’s Description
property allows you to attach a descriptive string to a web method. This description will appear on the web service help page when you test the web service in a browser.
When a representation of the web service is encoded into the SOAP message that is sent out to potential consumers, the WebMethod Description
property is included:
<WebMethod(Description:="Returns the stock price for the input stock symbol.")>
The WebMethod
attribute’s EnableSession
property defaults to False
.
If set to True
and if your web service inherits from WebService
, the session-state collection can be accessed with the WebService
.Session
property. If the web service does not inherit from the WebService
class, then the session-state collection can be accessed directly from HttpContext.Current.Session
.
To see this at work, you’ll add a method that tells you how many times it has been called in the current session:
<WebMethod(Description:="Number of hits per session.", EnableSession:=True)> _ Public Function HitCounter() As Integer If Session("HitCounter") Is Nothing Then Session("HitCounter") = 1 Else Session("HitCounter") = CInt(Session("HitCounter")) + 1 End If Return CInt(Session("HitCounter")) End Function
Session state is implemented via HTTP cookies in ASP.NET web services, so if the transport mechanism is something other than HTTP (say, SMTP), then the session-state functionality will not be available.
ASP.NET web methods can use transactions, but only if the transaction originates in that web method. In other words, the web method can only participate as the root object in a transaction. This means that a consuming application cannot call a web method as part of a transaction and have that web method participate in the transaction.
The WebMethod
attribute’s TransactionOption
property specifies whether or not a web method should start a transaction. There are five legal values of the property, all contained in the TransactionOption
enumeration. However, because a web method transaction must be the root object, there are only two different behaviors: either a new transaction is started (Required
or RequiresNew
) or it is not (Disabled
, NotSupported
, or Supported
).
To use transactions in a web service, you must take several additional steps.
Add a reference to System.EnterpriseServices.dll. In Visual Studio .NET, this is done through the Solution explorer or the Project → Add Reference menu item. Add the System.EnterpriseServices
namespace:
Imports System.EnterpriseServices
You must also add a TransactionOption
property with a value of RequiresNew
or Required
to the WebMethod
attribute:
<WebMethod(TransactionOption:=TransactionOption.RequiresNew)>
If there are no exceptions thrown by the web method, then the transaction will automatically commit unless the SetAbort()
method is explicitly called. If an unhandled exception is thrown, the transaction will automatically abort.
As with aux class, it is possible to have more than one method or function defined in your web service class with the same name (method overloading ). They are differentiated by their signature (the number, data type, and order of their parameters).
Unfortunately, method overloading is not supported by the standard industry protocols, so if you do overload a web method, you must provide each overloaded version with its own unique MessageName
property. When the overloaded method is referred to in SOAP messages, the MessageName
will be used, and not the method name.
To see the MessageName
property at work, you’ll add an overloaded method named GetValue
. The first overload accepts the stock name as a parameter (presumably it looks up the user’s account in the database, and returns the value of that stock). The second overload passes in not only the stock name, but also the number of shares owned. The overloaded methods
are shown in Example 14-4.
<WebMethod(Description:="Returns the value of the users holdings " & _ "in a specified stock symbol.", _ MessageName:="GetValueStockInPortfolio")> _ Public Function GetValue(ByVal StockSymbol As String) As Double ' Code stubbed out Return 0 End Function <WebMethod(Description:="Returns the value of a specified number " & _ "of shares in a specified stock symbol.", _ MessageName:="GetValueThisManyShares")> _ Public Function GetValue( _ ByVal StockSymbol As String, _ ByVal NumShares As Integer) As Double ' Code stubbed out Return 0 End Function
Because you overloaded GetValues
in the web service class, what you see displayed is the MessageName
property, as shown in Figure 14-2).
Before you create a client for your web service, you can have Visual Studio 2005 create a test page for you. Just run the program in the debugger, as shown in Figure 14-2.
Click on GetName
, for example. You will be prompted to enter a stock symbol, as shown in Figure 14-3.
Enter a valid stock symbol (one of the symbols in the array) and the stock’s name is returned in an HTML document, as shown in Figure 14-4.
You can examine the WSDL (Web Service Description Language) document for this web service by adding ?WSDL after the URL, as shown in Figure 14-5.
There are three things to notice in Figure 14-5. First, the URL is identical to the URL that brought up the test document, except that the string ?WSDL is appended. Second, both the GetValueStockInPorffolio
and GetValueThisManyShares
methods are shown (the names used in the MessageName
property), but the GetValue
method does not appear. The SOAP document (and thus the WSDL document) does not use the class’s method name, but rather uses the unambiguous MessageName
property.
To create a client that uses your new web service, create a new (normal) ASP.NET web site called StockPriceClient
, as shown in Figure 14-6.
Your client will need knowledge of your web service. The easiest way to provide that knowledge is to create a web reference. Right-click on your project and choose Add Web Reference, as shown in Figure 14-7.
The Add Web Reference dialog will open. If you have created the web service on the same machine as the client, click on “web services on the local machine,” as shown in Figure 14-8.
If you have been using file-based web applications, you’ll need to create a virtual directory in IIS to point to the web service, for this web reference to work.
Once you click on Service, the Add Web Reference dialog will locate the service (and bring up the test page). Give your service a reference name (a name by which you can refer to it in your code) and click Add Reference, as shown in Figure 14-9.
When you click Add Reference, the web reference is added to your project, and reflected in the Solution explorer, as shown in Figure 14-10.
Switch to Design view. Enter the words Stock Ticker and highlight them. Set them to Heading 1 and center them using the toolbar controls.
Move down on the page and enter the text Please enter a stock symbol:
, then drag a text box into place. Name the text box txtStockSymbol
. Add a button, name it btnSubmit
, and set its text to Get Stock Info
. Finally, add a label (lblMsg
) and set its text to blank (see Figure 14-11).
Double-click on the button to create an event handler. In the event handler, you’ll get the text from txtStockSymbol
and send it to the GetName
and GetPrice
methods of your service. You’ll then format the response and put it in the output label, as shown in Example 14-5.
Protected Sub btnSubmit_Click( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnSubmit.Click Dim proxy As StockPriceService.Service = New StockPriceService.Service() Dim symbol As String = Me.txtStockSymbol.Text Dim stockName As String = proxy.GetName(symbol) Dim stockPrice As Double = proxy.GetPrice(symbol) Me.lblMsg.Text = "The price of " & stockName & " is " & stockPrice.ToString() End Sub
The result is that when you put in a stock symbol and press the Get Stock Info button, the methods of the web service are called, and values are returned to your client application for display, as shown in Figure 14-11.