The ASP Column

Web Services: ATL Server Versus ASP.NET

George Shepherd

Code download available at:ASPColumn0402.exe(630 KB)

Contents

Web Services
ASP.NET Web Service Architecture
Web Methods
Managing State in ASP.NET
Consuming the Web Service
Managing Headers in ASP.NET
ATL Server Web Service Architecture
Managing State in ATL Server
Consuming the Web Services
Managing Headers in ATL Server
Conclusion

In the November 2003 issue of MSDN® Magazine I looked at the basic architectural differences between ATL Server and ASP.NET to explore two modern approaches to developing Web sites using Microsoft technologies. I looked primarily at developing a forms-based user interface with each framework. ASP.NET centers around the ASP.NET pipeline and some classes running in the common language runtime (CLR). ATL Server uses custom ISAPI extension DLLs and server response files (SRFs). This month I'm going to compare building Web services using ATL Server versus building them with ASP.NET.

Web Services

A Web service is basically a Web site that can interact with other computers and other remote applications. In a way, Web services represent a universal remote procedure call (RPC) mechanism.

The notion of calling methods between different computers is not new. The raw Win32®-based RPC mechanism in the early 1990s and DCOM throughout the mid-to-late 90s represented useful ways to invoke methods across hosts running Windows® operating systems. Unfortunately, these mechanisms did not work well for invoking methods between Windows-based machines and other vendors' systems (CORBA-based systems, for example). While several vendors offered bridge products to allow systems on different kinds of networks to interoperate, some were inconvenient and some simply didn't work. The fundamental problem of connecting multiple computers on heterogeneous networks basically boils down to incompatible wire formats and connection protocols.

Though a number of different protocols and wire formats have evolved over time, the industry is settling upon SOAP as a common wire format with HTTP as the connection protocol.

The beauty of Web services is that it doesn't matter how the endpoints implement the service because they agree on HTTP as the connection protocol and SOAP as the message format. Web services may be implemented any number of ways. For example, if you want to do all the work yourself, you could program a Web service using some socket code and an XML parser. However, that's a lot of work. Web service plumbing generally remains constant across implementations. As long as the plumbing intercepts a remote call, correctly unpacks the SOAP envelope, and turns the message into a real method call on the host machine, the client doesn't need to know nor does it need to care what goes on behind the scenes.

ASP.NET and ATL Server represent two separate ways of implementing Web services using Microsoft technologies. This month's sample code includes two similar applications, one written using ASP.NET and the other using ATL Server. Both apps demonstrate the canonical online Web service calculator. The calculator functions include the requisite Add and Subtract functions. They also include a method for calculating a specific prime number The applications illustrate the similarities and differences between the two frameworks. Using them, I'll illustrate the process of exposing methods, managing state in a Web service, and implementing SOAP headers. First I'll cover these techniques in ASP.NET.

ASP.NET Web Service Architecture

ASP.NET was designed with extensibility in mind. It is built around a well-defined pipeline that's capable of responding to virtually any kind of HTTP request. If you think about it, a SOAP request is just another HTTP request (albeit containing a SOAP envelope). Web services fit very cleanly into the ASP.NET pipeline. Figure 1 shows the ASP.NET architecture.

Figure 1 ASP.NET

Figure 1** ASP.NET **

You have a couple of options when building Web services with ASP.NET. The first option is to use the [WebMethod] attribute and the optional System.Web.Services.WebService class. The second option is to write your own HTTP handler to manage the service calls. I'll focus on the first approach, which is more common. I'll cover writing custom SOAP handlers in depth in a future column.

The Visual Studio® .NET wizards include a template for building ASP.NET Web services. The wizard pumps out an ASP.NET project including an ASMX file and codebehind file. The ASMX file includes the Web service directive:

<%@ WebService Language="cs" class="TheWSClass" %>

This directive tells ASP.NET which class contains the Web methods to expose. The codebehind file includes the implementation of this class, optionally deriving from System.Web.Services.WebService. The wizards even stub out a method that's exposed by the [WebMethod] attribute to illustrate the syntax.

Web Methods

Probably the easiest way to get a Web service up and running is to use the [WebMethod] attribute in ASP.NET. WebMethods map SOAP messages to methods on a .NET class. To expose a method through SOAP, just add the WebMethod, as shown in the Calculator class in Figure 2. (To download all the code samples in this column, see the link at the top of this article.)

Figure 2 Using the WebMethod Attribute

// Web service calculator public class Calculator : System.Web.Services.WebService { [WebMethod] int Add(int x, int y) { return x + y; } [WebMethod] int Subtract(int x, int y) { return x - y; } [WebMethod] public long NthPrime(long nth) { long p = -1; // Calc the prime number natively // calculate natively... CNthPrime nthprime; nthprime = new CNthPrime(); p = nthprime.GetNthPrime(nth); nthprime = null; return p; } }

The Calculator class exposes several methods as SOAP methods. Notice that the Calculator class derives from System.Web.Services.WebService. This class exposes to the service common ASP.NET objects such as application and session state which are also accessible through Http Context Current. Deriving from WebService is a convenience, not a necessity. The Add, Subtract, and NthPrime methods are preceded by the WebMethod attribute. Add and Subtract are fairly straightforward. Notice that the NthPrime method uses a class named CNthPrime. Finding primes is a bit more complicated—I'll cover that in a minute.

When a client sends a SOAP message to the ASP.NET Web service, it includes the name of the method in two places: in the SOAPAction header and in the request element's name. By default, ASP.NET uses the SOAPAction header to determine which method in the class to call (of course, the method must be marked with the WebMethod attribute). The handler uses .NET reflection to find the method in the class being called by the client. After determining which WebMethod to call, the handler deserializes the SOAP message into .NET objects that can be supplied during method invocation.

For example, in the calculator example in Figure 2, the handler maps parameters for the Add method (both defined as int by the message schema) to .NET values which become the managed parameters used to call the Add method.

Managing State in ASP.NET

Using the WebMethod attribute to expose methods greatly simplifies the process of writing Web services. In addition, WebMethods have access to all other aspects of the .NET Framework, including session and application state. Let's see how that might be useful.

In the calculator example, the site exposes a method for finding a specified prime number. One of the most common algorithms for finding a prime number is the Sieve of Eratosthenes. To find prime numbers, the algorithm uses a couple of integer arrays, eventually storing the prime numbers in one of them. Because the algorithm for finding the nth prime number involves finding all the prime numbers up to that point, running the algorithm once for each separate request is inefficient. Figure 3 shows an ASP.NET class for finding prime numbers.

Figure 3 Find and Store Primes

class CNthPrime { long[] primes; long i, k; public long[] FindPrimes() { HttpContext ctx = HttpContext.Current; primes = (long[])ctx.Cache["primes"]; if(primes == null) { primes = new long[1029]; ctx.Cache["primes"] = primes; // Find the primes here... return primes; } return (long[])ctx.Cache["primes"]; } public long GetNthPrime(long nth) { primes = FindPrimes(); if(nth < 1028) { return(primes[nth]); } else { return -1; } } }

The CNthPrime class calculates an array of long numbers representing prime numbers in ascending order. To avoid recalculating the prime numbers each time the method is called, the class stores the array in the ASP.NET cache. (Web methods have access to the global cache, application state, and session state.) Subsequent requests for a prime number are much faster because the numbers have already been calculated and all that's required is a cache hit. The Calculator class uses the CNthPrime class to find the nth prime number in a Web method.

Consuming the Web Service

Once a service is exposed over the Web, it can be consumed by any other application that can find it and that can understand the SOAP protocol. Because crafting SOAP consumers by hand is a tedious and error-prone process, both ASP.NET and ATL Server include client proxy generators. ASP.NET utilizes the System.Web.Services.Description.ServiceDescriptionImporter class. The proxy generator in ATL Server is named SPROXY.EXE.

The ASP.NET Web service example calculates the prime numbers in two ways: using the CNthPrime class and by delegating to the other Web service example from this month (the ATL Server example). It's very straightforward to build a proxy from the ATL Server project. Just add the Web Reference to the project using the Project Explorer. Visual Studio finds the application, retrieves the Web Services Description Language (WSDL), and generates a proxy class. (In addition, the ASP.NET Calculator will be used by the ATL Server Calculator in the same way. That is, the ATL Server Calculator will calculate prime numbers using both CNthPrime and the ASP.NET service.)

The ASP.NET Calculator Web service decides which method to use based on information that comes through some SOAP headers, as you'll see next.

Managing Headers in ASP.NET

Like most method invocation mechanisms, Web service methods are defined by a name and a set of parameters. For example, if you're writing a Web service to validate credit card numbers, you'd probably include the credit card number as a parameter and perhaps return a Boolean. What if you wanted to include some other information in the method call that wasn't directly related to the method call? An example might be that you'd like to make sure that only those clients authorized to look up credit information do so. You might restrict clients by having them pass a hash or some kind of token. On one hand, you could include the hash as a parameter. However, a better approach is to pass the information in SOAP headers. ASP.NET is very flexible when it comes to managing different kinds of SOAP headers.

At this point in time, the standard SOAP specification doesn't list what kinds of things should go in the header. SOAP headers exist to assist the server in handling the request. You'll often find authentication information, client identification information, transaction information, and so forth in the SOAP header.

ASP.NET automates handling SOAP headers with the SoapHeader class and the SoapHeaderAttribute attribute. For example, imagine you wanted to make sure that only certain clients have access to the NthPrime method. You could provide those clients with a special token and require that token be passed as part of a SOAP header. The code in Figure 4 shows how to define and use a SOAP header. It also shows that you can expose SOAP headers to other applications by defining a class representing the data bound for the header. In this case, the information going into the header is a key for unlocking the method and a Boolean flag indicating whether to calculate the number locally or by delegating to another Web service. Notice that the class declares a member variable of type CalcHeader and refers to the member variable in the [SoapHeader] attribute.

Figure 4 Using SOAP Headers in ASP.NET

using localhost; public class CalcHeader : SoapHeader { public int nKey; public bool bDelegateToOtherWS; } public class Calculator : System.Web.Services.WebService { public CalcHeader calcHeader; // Add and Subtract removed for clarity... [WebMethod] [SoapHeader("calcHeader", Required=true)] public long NthPrime(long nth) { long p = -1; // client must provide token // 12345 to get the nth prime if(calcHeader !=null && calcHeader.nKey == 12345) { if(calcHeader.bDelegateToOtherWS == true) { ATLServerOnlineCalcService atlscalc; try { atlscalc = new ATLServerOnlineCalcService(); atlscalc.CalcHeaderValue = new CalcHeader(); atlscalc.CalcHeaderValue.m_bDelegateToOtherWS = false; atlscalc.CalcHeaderValue.m_lKey = 12345; p = atlscalc.NthPrime((int)nth); } catch(Exception ex) { System.Console.WriteLine(ex); } } else { // Calc the prime number natively CNthPrime nthprime; nthprime = new CNthPrime(); p = nthprime.GetNthPrime(nth); nthprime = null; } } return p; } }

The second aspect is using SOAP headers from the client's point of view. In addition to exposing the SOAP headers to potential clients, the NthPrime method bases its own decisions on how to calculate the primes using the information provided in the headers. In this case, NthPrime is behaving as a client. The NthPrime method tests the key and examines bDelegateToOtherWS to figure out what to do. If the Web service client has asked to use the other Web service (the ATL Server Calculator), then the method uses the generated proxy class and generated SOAP header-management classes. NthPrime sets up the SOAP headers to send to the ATL Server Calculator.

ATL Server Web Service Architecture

Now let's take a look at the ATL Server architecture. As you saw earlier, ATL Server is another framework for handling HTTP requests. In the November 2003 installment of The ASP Column I looked at how ATL Server handles standard (UI-oriented) requests with standard HTML. ATL Server also handles method-oriented SOAP requests. ATL Server-based Web services use much the same architecture to handle SOAP requests. That is, the application consists of an ISAPI DLL and one or more application DLLs that run in an IIS virtual directory. However, ATL Server-based Web services don't use SRFs. Remember, SRFs incorporate UI tags and call into the application DLLs for processing the request. Figure 5 shows the ATL Server architecture.

Figure 5 ATL Server

Figure 5** ATL Server **

Web service-based methods are called directly from the client computer and are generally not related to UI tags. Developing ATL Server-based Web services is in some ways similar to developing ASP.NET Web services.

In ATL Server, the programmatic interface to the Web service is represented as a COM interface. The methods are implemented in a class derived from the interface. The methods to be exposed are marked using the [soap_method] attribute, as shown in Figure 6.

Figure 6 An ATL Server-based Web Service Class

class CATLServerOnlineCalcService : public IATLServerOnlineCalcService { public: [ soap_method ] HRESULT Add(LONG x, LONG y, LONG* pResult) { *pResult = x + y; return S_OK; } [ soap_method ] HRESULT Subtract(LONG x, LONG y, LONG* pResult) { *pResult = x - y; return S_OK; } [ soap_method ] HRESULT NthPrime(LONG nth, LONG* plPrime) { CNthPrime* pnthprime; pnthprime = new CNthPrime(); *plPrime = pnthprime->GetNthPrime(nth, m_spBlobCache); delete pnthprime; return S_OK; } }; // class CATLServerOnlineCalcService

The [soap_method] injects code into the class for parsing the SOAP method. The injected code unpacks the SOAP message, places parameters on the internal call stack, executes the method, and returns the result as a SOAP response. ATL Server uses reflection to accomplish this. That's why the SOAP methods need to appear in a COM interface—so they have type information generated for them.

Managing State in ATL Server

The ATL Server application uses the same strategy for optimizing performance of the prime number calculator as does the ASP.NET application. Rather than calculating the prime numbers each time the method is called, the ATL Server application calculates the prime numbers once and stores them for later. The code in Figure 7 shows how the ATL Server application stores the prime numbers in the ATL Server BLOB cache (which is very similar in function to the ASP.NET data cache).

Figure 7 Storing Primes in the ATL Server BLOB Cache

class CNthPrime { long rgprimes[8193]; public: void FindPrimes(CComPtr<IMemoryCache> spBlobCache) { HCACHEITEM hItem; if(FAILED(spBlobCache-> LookupEntry("rgprimes", &hItem))) { CalcPrimes(); StorePrimes(spBlobCache); return; } void* pData = NULL; DWORD dwSize; HRESULT hr = spBlobCache->GetData(hItem, &pData, &dwSize); if (FAILED(hr) || pData == NULL) { spBlobCache->ReleaseEntry(hItem); CalcPrimes(); StorePrimes(spBlobCache); return; } memcpy(rgprimes, pData, sizeof(long) * 8193); spBlobCache->ReleaseEntry(hItem); } void CalcPrimes() { // CalcPrimes removed for clarity } void StorePrimes( CComPtr<IMemoryCache> spBlobCache) { DWORD dwSize = sizeof(long)*8193; long* pData = static_cast<long*>(::HeapAlloc(::GetProcessHeap(), 0, dwSize)); memcpy(pData, rgprimes, dwSize); FILETIME ft = {0}; HRESULT hr = spBlobCache->Add("rgprimes", pData, dwSize, &ft, 0, 0, NULL); } long GetNthPrime(long nth, CComPtr<IMemoryCache> spBlobCache) { FindPrimes(spBlobCache); return(rgprimes[nth]); } };

Like the ASP.NET application, the ATL Server application uses a utility class for calculating the prime numbers. The code generated by Visual Studio stubs out smart pointers to ATL's caching, browser capabilities, and session state services. In this case, the utility class is using the ATL Server BLOB cache facilities represented by the m_spBlobCache member of the Web service. The ATL Server application first checks the BLOB cache for the array of primes. Only if the array is not found does the application take the time to generate the array.

Using the BLOB cache is more complex than using the ASP.NET data cache. Remember, ATL Server is a C++ framework, so all the rules for managing low-level pointers apply. The BLOB cache requires a HANDLE-based approach to storing data. However, the ATL Server BLOB cache is similar to the ASP.NET data cache in that they both are string key/value collections.

Consuming the Web Services

The ATL Server-based calculator also provides the same choice to clients as to how the actual prime number calculation takes place. It's calculated either within the ATL Server application itself or it's delegated to the ASP.NET-based Web service. The ATL Server application holds a proxy class generated from the ASP.NET application (just as the ASP.NET application holds a proxy class generated from the ATL Server application). The ATL Server application uses the proxy during the prime number calculation; this is shown in Figure 8.

Figure 8 Managing SOAP Headers in ATL Server

class CATLServerOnlineCalcService : public IATLServerOnlineCalcService { public: class CalcHeader { public: long m_lKey; bool m_bDelegateToOtherWS; }; CalcHeader m_calcheader; public: [ soap_method ] [soap_header(value="m_calcheader", required=true, in=true, out=false)] HRESULT NthPrime(LONG nth, LONG* plPrime) { *plPrime = -1; HRESULT hr = S_OK; int nUseWS = 0; if(m_calcheader.m_lKey == 12345) { // run prime number algorithm here... if(this->m_calcheader.m_bDelegateToOtherWS) { // Use the ASP.NET Web service here... Calculator::CCalculator calc; calc.CalcHeader0.nKey=12345; calc.CalcHeader0.bDelegateToOtherWS = false; __int64 l; hr = calc.NthPrime(nth, &l); *plPrime = l; } else { // calculate natively... CNthPrime* pnthprime; pnthprime = new CNthPrime(); *plPrime = pnthprime->GetNthPrime(nth); delete pnthprime; } } return hr; } }; // class CATLServerOnlineCalcService

Managing Headers in ATL Server

The decision about how to calculate the prime numbers is made by examining information passed through the SOAP headers. ATL Server automates managing SOAP headers in much the same way that ASP.NET does—by defining a class to represent the header and by using the [soap_header] attribute, as shown in Figure 8.

The ATL Server approach to managing headers is almost identical to the ASP.NET approach. The CalcHeader class defines a key and a Boolean flag. The Web service class includes a member variable of type CalcHeader, which is mentioned in the [soap_header] attribute. ATL Server populates the m_calcheader class using information provided by clients through the header.

Conclusion

In the November 2003 column I compared the implementation of a UI-based site using ATL Server and ASP.NET. This time I looked at both frameworks from the Web services perspective. Both are fairly effective when it comes to building Web services and both are comparable in functionality. ATL Server and ASP.NET provide access to session state and support BLOB-style data caching and SOAP methods and headers.

I've found that it's generally much easier to get a Web service going using ASP.NET. Writing managed code tends to be simpler than dealing with pointers and the other minutiae involved with C++ code. On the other hand, if you find yourself firmly bound to C++ without the option of going to managed code, or if you need very fine control of the code in your application, ATL Server is an attractive option for building Web sites and Web services. It's important to note, however, that when "Indigo" (code name for the communications subsystem for the next release of Windows) was announced at this year's Professional Developer's conference, Microsoft articulated the plans for migrating code from a variety of technologies to Indigo. These include ASMX but not ATL Server.

Send your questions and comments for George to  asp-net@microsoft.com.

George Shepherd specializes in software development for the .NET Framework. He is the author of Programming with Microsoft Visual C++ .NET (Microsoft Press, 2002) and coauthor of Applied .NET (Addison-Wesley, 2001). He teaches seminars with DevelopMentor and is a contributing architect for Syncfusion's .NET tools.