Remote Method Calls with ASP.NET AJAX
Overview
The chief factor for the success and rapid adoption of the AJAX model is that it enables most application tasks to be performed on the client with limited exchange of data with the server. Nonetheless, some data exchange with the server environment is necessary.
As discussed in Chapter 4, “Partial Page Rendering,” ASP.NET AJAX applications can use asynchronous postbacks to refresh portions of the page and update the state of controls through lightweight server events. An AJAX postback is more lightweight than a full postback, but it’s still a request that moves view state, event validation data, and any other input fields you might have around the page. Also, the AJAX postback is still a request that goes through the full server-side page life cycle. It differs from a regular ASP.NET postback only because it has a custom rendering phase and, of course, returns only a portion of the whole page markup. Put another way, an AJAX postback is definitely faster and much more beneficial than regular postbacks, but it’s still subject to a number of constraints. And, more importantly, it doesn’t fit just any scenario.
If the page has a huge view state, for example, the benefits of AJAX postbacks are quite watered down. With 50 KB of view state, can you really consider saving one or two KB of markup to be a success? On the other hand, the partial rendering model doesn’t require the developer to learn new skills, has a surprisingly short learning curve, and has limited impact on existing code. It gives you an easy way of updating the state of controls during the roundtrip and, subsequently, on the user interface.
It turns out that there are situations in which the partial rendering model is not appropriate and other situations in which it is just perfect. When the client requires that a specific operation be executed on the server with no frills and in a purely stateless manner, you should consider other options. Enter remote server method calls.
Making a call to a remote server requires that a public, well-known application programming interface (API) be exposed and made accessible from JavaScript. ASP.NET AJAX Extensions supports two server APIs: Web services and page methods.
In this chapter, we’ll explore the connections between the ASP.NET AJAX framework and the world of remote services. You’ll learn how to configure an ASP.NET AJAX application to consume local services and bridge Web services hosted by other applications or deployed on external servers.
Designing the Server API for Remote Calls
As we saw in Chapter 6, “Built-in Application Services,” ASP.NET AJAX Extensions sports a few built-in Web services for user authentication and profiling. These services are implemented as ASP.NET Web services local to the calling application. The Microsoft AJAX library also features two matching JavaScript classes acting as the proxies of such services. The same model is used to back client JavaScript calls to a custom ASP.NET remote API.
The first step entails that you design a public API to be invoked from the client. To define this API, an explicit contract is not required for version 1.0 of ASP.NET AJAX Extensions. However, I have a couple of good reasons to recommend that you start from this moment forward to design the public API as an interface. One reason is that it generates cleaner code. Another reason is that with the next version of ASP.NET (code-named “Orcas”), you’ll be allowed to link primarily to Windows Communication Foundation (WCF) services, where an explicit contract for the interface is mandatory.
When you’re done with the interface of the server API, you proceed with the creation of a class that implements the interface. Finally, you publish the remote API and let the ASP.NET AJAX runtime manage calls from the client. Let’s expand on the various steps.
Important |
Too often, ASP.NET AJAX articles and documentation refer to the platform’s ability to invoke Web services from the client. But this is a bit misleading. You can’t just invoke any Web service you like. If this is so (and it is), one rightly wonders why the simple and clear Web service model is then subject to a number of seemingly arbitrary ASP.NET AJAX limitations. For example, as you start working with Web services in AJAX, you quickly find that the Web services you can invoke are meant to be ASP.NET AJAX Web services, and what’s more, they must be local to the host application. If your intention was to use a public Web service (say, Amazon), you realize that you need a separate download and a distinct (Amazon) programming model. And you probably will wonder why on earth is this the case? After all, the articles and documentation make it appear that any Web service will work. In my opinion, all this confusion stems from a flawed approach to the question. ASP.NET AJAX doesn’t just let you call into any Web service from JavaScript. Rather, the way to look at the situation is to imagine that ASP.NET AJAX lets you use JavaScript to place calls into some server code within your own application. The application server code, in turn, can be exposed as an ASP.NET AJAX Web service local to the application. External Web services, those being services outside your application’s domain, cannot be invoked directly from the client for security reasons. This is by design. |
Defining the Contract of the Remote API
A contract is used to specify what the server-side endpoint exposes to callers. An explicit contract is required for WCF services but is expressed in a less formal, implicit way for general Web services. In the context of an ASP.NET AJAX server API, a contract is mostly a way to write cleaner code. It’s not required, but it can help and is definitely a practice worth pursuing. This is especially true in light of the next version of ASP.NET, where the integration with WCF services and the ability to invoke service endpoints from the client are the pillars.
The Contracted Interface
A contract is defined through an interface that groups methods and properties that form the server API. Here’s an example of a simple service that returns the current time on the server:
using System; public interface ITimeService { DateTime GetTime(); string GetTimeFormat(string format); }
The contract exposes two methods: GetTime and GetTimeFormat. These methods form the server API that can be called from within the client.
Warning |
Currently, you are on your own when implementing a given interface in an AJAX server API. There’s no automatic runtime check to enforce the requirement that exactly those methods are exposed by the server API. If you think of the process in terms of contracts and operations within contracts however, having contracts helps a lot. You’ll write cleaner code to begin with and find your code has laid the groundwork for migrating to future evolutions of the .NET and ASP.NET platforms. |
What’s Coming Out with “Orcas”?
From an AJAX perspective, the next innovation in the “Orcas” release of the .NET platform (presumably in late 2007) is the tight integration between the AJAX platform and WCF services. In the near future, WCF services will become the primary back-end environment for AJAX-driven client calls.
As we’ll see in the remainder of the chapter, with the current version 1.0 of ASP.NET AJAX Extensions, remote calls target the contract that is either exposed by a local application-specific Web service or incorporated in the page’s code-behind class. These two models will be joined, or even replaced, by a new model entirely based on WCF services and a new runtime infrastructure in ASP.NET “Orcas.”
According to the upcoming model, you define your server API as a WCF service and give it a well-known contract. You expose the service to browser clients by registering the service’s endpoint with the script manager. In the configuration file, then, you can make deployment-time decisions on bindings and behaviors for the service. The JavaScript proxy model in vogue today will work unchanged. For a page developer, all that apparently changes is merely the URL to register with the script manager. For a server developer, the change is all about a different set of attributes used to decorate the service class. The infrastructure in between the browser and the server environment, though, will be significantly enhanced.
In the end, you’ll have a great server programming model with WCF that is also fully supported by DLINQ and Windows Workflow Foundation (WF). In addition, it can expose the same service, at the same time, as a service based on Simple Object Access Protocol (SOAP) to any client and as a JSON-based service to AJAX clients. Plus, and what an extraordinary feature this is, you get to reuse all the great features of the WCF platform: manageability, tracing, logging, throttling, timeouts, Denial-of-Service (DoS) protection quotas, and hosting options.
Why Not WSDL and SOAP?
You can use Web services to define the server API that an ASP.NET AJAX application can invoke via JavaScript, but this doesn’t mean that you can call just any Web services from JavaScript. The JavaScript proxy class used by the page script is generated by the script manager class, and it requires the ASP.NET AJAX Extensions to be installed on the server. Why not use Web Services Description Language (WSDL) to expose the contract and use SOAP (instead of JSON) to compose messages?
Being XML-based, WSDL and SOAP are hard to manage from the client and when using the JavaScript language. JSON is a much simpler format that, more importantly, finds a natural serializer-the eval function-in any JavaScript engine. As we’ll see later in the chapter, you need an extra layer of code to call any remote Web services, regardless of location and platform. This additional layer of code is managed code that runs on the server and that provides a proper JavaScript proxy for client-driven calls. We’ll return to this point later in the “Bridging External Web Services” section and also, from an architectural perspective, in Chapter 8, “Building AJAX Applications with ASP.NET.”
Implementing the Contract of the Remote API
Once you have defined the server API you want to invoke from the client, you implement and expose it. As mentioned, this can be done in either of two ways: using an application-local ASP.NET Web service, or using a set of static methods on the code-behind class of each page interested in making any calls.
Contract via Web Services
An ASP.NET Web service is usually implemented through a .NET class that derives from the WebService base class:
using System.Web.Services; public class TimeService : WebService, ITimeService { ... }
To direct the Web service to support a given interface, you simply add the interface type to the declaration statement and implement corresponding methods in the body of the class.
Again, the implementation of a user-defined interface in an ASP.NET Web service is not strictly required. However, especially for Web services that represent the server API in the context of ASP.NET AJAX applications, the implementation of an interface is a good thing that keeps code neat and even elegant. And doing so prepares you and your code for future enhancements of the platform.
Note |
The WebService base class is optional and serves primarily to provide direct access to common ASP.NET objects, such as Application and Session. If you don’t need direct access to the intrinsic ASP.NET objects, you can still create an ASP.NET Web service without deriving from the WebService class. In this case, you can still use ASP.NET intrinsics through the HttpContext object: |
Contract via Code-Behind Classes
A Web service forms an extra layer of code in an ASP.NET application. If you don’t want to define such a layer, you can still define your server API via static methods exposed directly by the code-behind class of a given page. Needless to say, if you opt for static methods you’ll be able to call server methods only from within the page that defines them. No cross-page calls are supported. If you need a centralized location for the server API that is visible to all pages in the application, you have to go for the Web service interface.
By design and mostly because of performance considerations, client-callable page methods need to be static. In a .NET interface, though, static methods are not allowed. How can you then expose a contract out of a code-behind class? The following code shows the typical prototype of a code-behind class with a client-callable Web method:
public partial class SamplePage : System.Web.UI.Page { [WebMethod] public static DateTime GetTime() { ... } ... }
To ensure that all methods in a given interface are exposed by the page, you first define a base class with the desired set of static Web methods:
public class TimeServicePage : System.Web.UI.Page { [WebMethod] public static DateTime GetTime() { return DateTime.Now.AddYears(1); } }
Next, you change the parent of the code-behind class from System.Web.UI.Page to the contract-based class, as shown here:
public partial class SamplePage : TimeServicePage { ... }
All static methods are inherited, and the WebMethod attribute is automatically maintained on methods across class inheritance.
Publishing the Contract
Now that we have defined the formal contract and implementation of the server API of an ASP.NET AJAX application, one more step is left-publishing the contract. How do you do that?
Publishing the contract means making the server API visible to the JavaScript client page and subsequently enabling the JavaScript client page to place calls to the server API. From the client page, you can invoke any object that is visible to the JavaScript engine. In turn, the JavaScript engine sees any class that is linked to the page. In the end, publishing a given server contract means generating a JavaScript proxy class that the script embedded in the page can command.
When the server API is implemented through a Web service, you register the Web service with the script manager control of the ASP.NET AJAX page. In addition, you add a special HTTP handler for .asmx requests in the application’s web.config file.
When the server API is exposed via page methods, you need to explicitly enable page methods on the script manager and also add an HTTP module to the configuration file.
Let’s expand upon the topic of Web services and page methods by exploring a few examples.
Remote Calls via Web Services
Web services provide a natural environment for hosting server-side code that needs to be called in response to a client action such as clicking a button. The set of Web methods in the service refers to pieces of code specific to the application, including some parts of the application’s middle tier. In this context, the Web service is part of the application and lives on the same machine and AppDomain. Keep in mind, however, that an ASP.NET AJAX application calls its server code via Web services; it does not call into just any Web service in a SOAP-based, platform-independent manner.
Creating an AJAX Web Service
A Web service made to measure for an ASP.NET AJAX application is nearly identical to any other ASP.NET Web service you might write for whatever purposes. Two peripheral aspects, though, delineate a clear difference between ASP.NET AJAX Web services and traditional ASP.NET Web services.
First and foremost, when working with ASP.NET AJAX Web services, you design the contract of an ASP.NET AJAX Web service to fit the needs of a particular application rather than to configure the behavior of a public service. The target application is also the host of the Web service. Second, you use a couple of new attributes to decorate the class and methods of the Web service that are not allowed on regular ASP.NET Web services.
The effect of this is, in the end, that an ASP.NET AJAX Web service has a double public interface: the JSON-based interface consumed by the hosting ASP.NET AJAX application, and the classic SOAP-based interface exposed to any clients, from any platforms, that can reach the service URL.
The ScriptService Attribute
To create an ASP.NET AJAX Web service, you first set up a standard ASP.NET Web service project and then add a reference to the system.web.extensions assembly. Next, you import the System.Web.Script.Services namespace:
using System.Web.Script.Services;
The attribute that establishes a key difference between ASP.NET Web services and ASP.NET AJAX Web services is the ScriptService attribute. You apply the attribute to the service class declaration, as shown here:
namespace IntroAjax.WebServices { [WebService(Namespace = "http://introajax.book/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [ScriptService] public class TimeService : System.Web.Services.WebService, ITimeService { ... } }
The attribute indicates that the service is designed to accept calls from JavaScript-based client proxies. If the Web service lacks the attribute, no JavaScript proxy class will ever be generated for it, regardless of whether or not the Web service is registered with the page. Without a JavaScript proxy class, no client call is possible to the remote server API.
When flagged with the ScriptService attribute, an ASP.NET Web service supports two types of calls: classic calls via SOAP packets and client calls using JSON messages.
Important |
If not properly handled, ASP.NET AJAX Web services, with their dual personalities, might result in you configuring a security issue. If you simply consider the Web service to be a constituent part of the ASP.NET AJAX application, you need no special security barrier to keep unauthorized users out. The application through its authentication layer is responsible for protecting pages. A Web service, though, is also exposed to SOAP clients and is a public endpoint. For this reason, you should be careful with the methods you expose. You should avoid exposing sensitive pieces of the middle tier to the public without a well-configured security barrier. It is recommended, then, that you add to the Web service only methods that form a sort of user interface–level business logic, where no critical task is accomplished. In addition, you should consider adding a validation layer in the body of Web methods and perhaps using network-level tools to monitor calling IP addresses and, if needed, block some of them. |
The ScriptMethod Attribute
Public methods of the Web service class decorated with the WebMethod attribute are automatically added to the JavaScript proxy class and can subsequently be called from the client page. Any methods in the proxy class are invoked using the HTTP POST verb and return their values as JSON objects. You can change these default settings on a per-method basis by using an optional attribute-ScriptMethod.
The ScriptMethod attribute features three properties, as described in Table 7-1.
Property |
Description |
---|---|
ResponseFormat |
Specifies whether the response will be serialized as JSON or as XML. The default is JSON, but the XML format can come in handy when the return value of the method is XmlDocument. In this case, because XMLHttpRequest has the native ability to expose the response as an XML DOM, you save unnecessary serialization and deserialization via JSON. |
UseHttpGet |
Indicates whether an HTTP GET verb should be used to invoke the Web service method. The default is false, meaning that the POST verb is used. The GET verb poses some security issues, especially when sensitive data is being transmitted. All the data, in fact, is stored in the URL and is visible to everybody. |
XmlSerializeString |
Indicates whether all return types, including strings, are serialized as XML. The default is false. The value of the property is ignored when the response format is JSON. |
Because of the repercussions it might have on security and performance, the ScriptMethod attribute should be used very carefully. The following code uses the attribute without specifying nondefault settings:
[WebMethod] [ScriptMethod] public DateTime GetTime() { ... }
The WebMethod attribute is required; the ScriptMethod attribute is optional. You should use it only when you need to change some of the default settings, as explained in Table 7-1.
Registering AJAX Web Services
To place calls to an ASP.NET Web service from the client, you need a JavaScript proxy class. More precisely, all that you really need is the XMLHttpRequest object, the URL of the target Web service, and the ability to serialize input data to JSON and deserialize any received JSON response. However, if any input or output parameters of a method require a complex type (for example, a Customer type), you should be able to handle it too, both to and from JSON. As you can see, a proxy class and, more than just this, a server-side framework that governs the generation of the proxy is required.
To trigger the built-in engine that generates any required JavaScript proxy and helper classes, you register the AJAX Web service with the script manager control of each page where the Web service is required. You can achieve this both declaratively and programmatically. Here’s how to do it declaratively from page markup:
You add a ServiceReference tag for each Web service bound to the page and set the Path attribute to a relative URL for the .asmx resource. Each service reference automatically produces an extra
The /js suffix appended to the Web service URL instructs the ASP.NET AJAX runtime (specifically, an ad-hoc HTTP handler, as we’ll see in a moment) to generate and inject the JavaScript proxy class for the specified Web service. If the page runs in debug mode (which is discussed in Chapter 3, “The Pulsing Heart of ASP.NET AJAX”), the suffix changes to /jsdebug and a debug version of the proxy class is emitted.
By default, the JavaScript proxy is linked to the page via a
Because the Web service call proceeds asynchronously, you need callbacks to catch up in case of both success and failure. The signature of the callbacks is similar, but the internal format of the results parameter can change quite a bit:
function method(results, context, methodName)
Table 7-3 provides more details about the various arguments.
Argument |
Description |
---|---|
results |
Indicates the return value from the method in the case of success. In the case of failure, a JavaScript Error object mimics the exception that occurred on the server during the execution of the method. |
context |
The user context object passed to the callback. |
methodName |
The name of the Web service method that was invoked. |
Based on the previous code, if the call is successful, the methodCompleted callback is invoked to update the page. The result is shown in Figure 7-1.
Figure 7-1: A remote call from the client
Error Handling
The “failed” callback kicks in when an exception occurs on the server during the execution of the remote method. In this case, the HTTP response contains an HTTP 500 error code (internal error) and the body of the response looks like the following:
{"Message":"Exception thrown for testing purposes", "StackTrace":" at IntroAjax.WebServices.MyDataService.Throw() in d:\IntroAjax\App_Code\Services\MyDataService.cs:line 62","ExceptionType":"System.InvalidOperationException"}
On the client, the server exception is exposed through a JavaScript Error object dynamically built based on the message and stack trace received from the server. This Error object is exposed to the “failed” callback via the results argument. You can read back the message and stack trace through message and stackTrace properties on the Error object.
You can use a different error handler callback for each remote call, or you can designate a default function to be invoked if one is not otherwise specified. However, ASP.NET AJAX still defines its own default callback, which is invoked when it gets no further information from the client developer. The system-provided error handler callback simply pops up a message box with the message associated with the server exception. (See Figure 7-2.)
Figure 7-2: The system-provided JavaScript error handler
If you define your own “failed” callback, you can avoid message boxes and incorporate any error message directly in the body of the page.
Giving User Feedback
A remote call might take a while to complete because the operation to execute is fairly heavy or just because of the network latency. In any case, you might feel the need to show some feedback to the user to let her know that the system is still working. In Chapter 4, we saw that the Microsoft AJAX library has built-in support for an intermediate progress screen and also a client-side eventing model. Unfortunately, this functionality is limited to calls that originate within updatable panels. For classic remote method calls you have to personally take care of any user feedback.
You bring up the wait message, the animated GIF, or whatever else you need just before you call the remote method:
function takeaWhile() { // In this example, the Feedback element is a tag $get("Feedback").innerHTML = "Please, wait ..."; IntroAjax.WebServices.MySampleService.VeryLengthyTask( methodCompletedWithFeedback, methodFailedWithFeedback); }
In the “completed” callback, you reset the user interface first and then proceed:
function methodCompletedWithFeedback(results, context, methodName) { $get("Feedback").innerHTML = ""; ... }
Note that you should also clear the user interface in the case of errors. In addition to showing some sort of wait message to the user, you should also consider that other elements in the page might need to be disabled during the call. If this is the case, you need to disable them before the call and restore them later.
Handling Timeouts
A remote call that takes a while to complete is not necessarily a good thing for the application. Keep in mind that calls that work asynchronously for the client are not necessarily asynchronous for the ASP.NET runtime. In particular, note that when you make a client call to an .asmx Web service, you are invoking the .asmx directly. For this request, only a synchronous handler is available in the ASP.NET runtime. This means that regardless of how the client perceives the ongoing call, an ASP.NET thread is entirely blocked (waiting for results) until the method is done. To mitigate the impact of lengthy AJAX methods on the application scalability, you can set a timeout:
IntroAjax.WebServices.MySampleService.set_timeout(3000);
The timeout attribute is global and applies to all methods of the proxy class. This means that if you want to time out only one method call, you have to reset the timeout for all calls you’re making from the page. To reset the timeout, you just set the timeout property to zero:
IntroAjax.WebServices.MySampleService.set_timeout(0);
When the request times out, there’s no response received from the server. It’s simply a call that is aborted from the client. After all, you can’t control what’s going on with the server. The best you can do is abort the request on the client and take other appropriate measures, such as having the user try again later.
Considerations for AJAX Web Services
Now that we know how to tackle AJAX Web services, it would be nice to spend some time reflecting on some other aspects of them-for example, why use local services?
Why Local Web Services?
To make sure you handle AJAX Web services the right way, think of them as just one possible way of exposing a server API to a JavaScript client. You focus on the interface that must be exposed and then choose between ASP.NET Web services and page methods for its actual implementation. Looking at it from this angle, you might find it to be quite natural that the Web service has to be hosted in the same ASP.NET AJAX application that is calling it.
But why can’t you just call into any Web services out there? There are two main reasons: security and required support for JSON serialization.
For security reasons, browsers tend to stop cross-site calls. Most browsers bind scripted requests to what is often referred to as the “same origin policy.” Defined, it claims that no documents can be requested via script that have a different port, server, or protocol than the current page. In light of this, you can use the XMLHttpRequest object to place asynchronous calls as long as your request hits the same server that served the current page.
Because of the cross-site limitations of XMLHttpRequest in most browsers, ASP.NET AJAX Extensions doesn’t allow you to directly invoke a Web service that lives on another IIS server or site. Without this limitation, nothing would prevent you from invoking a Web service that is resident on any platform and Web server environment, but then your users are subject to potential security threats from less scrupulous applications. With this limitation in place, though, an additional issue shows up: the inability of your host ASP.NET AJAX environment to build a JavaScript proxy class for the remote, non–ASP.NET AJAX Web service.
Note |
Because of the impact that blocked cross-site calls have on general AJAX development, a new standard might emerge in the near future to enable such calls from the browser. It might be desirable that the client sends the request and dictates the invoked server accept or deny cross-site calls made via XMLHttpRequest. As of this writing, though, the possibility of direct cross-site calls from AJAX clients (not just ASP.NET AJAX Extensions) remains limited to the use of IFRAMEs and finds no built-in support in ASP.NET AJAX Extensions 1.0. |
Why JSON-Based Web Services?
A call to a Web service hosted by the local ASP.NET AJAX application is not conducted using SOAP as you might expect. SOAP is XML-based, and parsing XML on the client is very expensive in terms of memory and processing resources. It means that an XML parser must be available in JavaScript, and an XML parser is never an easy toy to build and manage, especially using a relatively lightweight tool such as JavaScript. So a different format is required to pack messages to be sent and unpack messages just received. Like SOAP and XML schemas together, though, this new format must be able to serialize an object’s public properties and fields to a serial text-based format for transport. The format employed by ASP.NET AJAX Web services is JavaScript Object Notation, or JSON for short. (See http://www.json.org for more information on the origin and goals of the format.)
JSON is a text-based format specifically designed to move the state of an object across tiers. It is natively supported by JavaScript in the sense that a JSON-compatible string can be evaluated to a JavaScript object quite simply through the built-in JavaScript eval function. If the JSON string represents the state of a custom object, it’s your responsibility to ensure that the definition of the corresponding class is available on the client.
The client-side ASP.NET AJAX network stack takes care of creating JSON strings for each parameter to pass remotely. The JavaScript class that does that is called Sys.Serialization.JavaScriptSerializer. On the server, ad hoc formatter classes receive the data and use .NET reflection to populate matching managed classes. On the way back, .NET managed objects are serialized to JSON strings and sent over. The script manager is called to guarantee that proper classes referenced in the JSON strings-the Web service proxy class-exist on the client.
Runtime Support for JSON-Based Web Services
As a developer, you don’t necessarily need to know much about the JSON format. You normally don’t get close enough to the heart of the system to directly manage JSON strings. However, a JSON string represents an object according to the following example schema:
{ "__type":"IntroAjax.Customer", "ID":"ANATR", "ContactName":"Ana Trujillo" ... }
You’ll find a number of comma-separated tokens wrapped in curly brackets. Each token is, in turn, a colon-separated string. The left part, in quotes, represents the name of the property; the right part, in quotes, represents the serialized version of the property value. If the property value is not a primitive type, it gets recursively serialized via JSON. If the object is an instance of a known type (that is, it is not an untyped JavaScript associative array), the class name is inserted as the first piece of information associated with the __type property. Any information being exchanged between an ASP.NET AJAX client and an ASP.NET AJAX Web service is serialized to the JSON format.
To the actual Web service, the transport format is totally transparent-be it SOAP, JSON, plain-old XML (POX), or whatever else. The runtime infrastructure takes care of deserializing the content of the message and transforms it into valid input for the service method. The ASP.NET AJAX runtime recognizes a call directed to an AJAX Web service because of the particular value of the Content-Type request header. Here’s an excerpt from the Microsoft AJAX client library where the header is set:
request.get_headers()['Content-Type'] = 'application/json; charset=utf-8';
The value of this header is used to filter incoming requests and direct them to the standard ASP.NET Web service HTTP handler or to the made-to-measure ASP.NET AJAX Web service handler that will do all the work with the JSON string.
Why Not WCF Services?
Windows Communication Foundation (WCF) is the .NET 3.0 API for building service-oriented applications. WCF should be seen as the sum of a number of existing Microsoft connectivity technologies that are unified and extended into a single programming model. WCF is independent of underlying communications protocols, and it lets native applications interoperate with other applications using open standards and protocols.
From this concise but effective description, WCF seems to be the perfect partner for an ASP.NET AJAX client. However, in ASP.NET AJAX Extensions 1.0, there’s no way to connect a JavaScript client to a WCF service. Well, not yet.
Starting with the next release of the .NET Framework (presumably in late 2007), the engine that allows ASP.NET AJAX clients to call remote methods will be redesigned to allow WCF services as yet another way to define the callable server API. An extended runtime engine will then capture local server calls and forward them to a remote WCF service hosted anywhere, even outside the ASP.NET AJAX application. The integration between ASP.NET AJAX and WCF services is on its way. It’s just a matter of a few months now.
Remote Calls via Page Methods
As we’ve seen, ASP.NET Web services are a simple and effective way of implementing a server API. Once the ASP.NET AJAX runtime engine has generated the proxy class, you’re pretty much done and can start calling methods as if they were local to the client. Web services, though, are not free of issues. They require an extra layer of code and additional files to be added to the project. Is this a big source of concern for you? If not, consider this instead: once deployed, the Web service is accessible via SOAP to any clients and from any platforms that can see the endpoint. This arrangement might or might not be a problem, as it mostly depends on what you expose through the Web service. In any case, you now have a less visible alternative-page methods-although it’s still not immune from replay attacks.
Creating Page Methods
Page methods are simply public, static methods exposed by the code-behind class of a given page and decorated with the WebMethod attribute. The runtime engine for page methods and AJAX Web services is nearly the same. If you want an explicit container of endpoints, you choose Web services; if you want an implicit host, you go for the page class. Using page methods saves you from the burden of creating and publishing a Web service; at the same time, though, it binds you to having page-scoped methods that can’t be called from within a page different from the one where they are defined. We’ll return later to the pros and cons of page methods. For now, let’s just learn more about them.
Defining Page Methods
Public and static methods defined on a page’s code-behind class and flagged with the WebMethod attribute transform an ASP.NET AJAX page into a Web service. Here’s a sample page method:
public class TimeServicePage : System.Web.UI.Page { [WebMethod] public static DateTime GetTime() { return DateTime.Now; } }
You can use any data type in the definition of page methods, including .NET Framework types as well as user-defined types. All types will be transparently JSON-serialized during each call.
Note |
The page class where you define methods might be the direct code-behind class or, better yet, a parent class. In this way, in the parent class you can implement the contract of the public server API and keep it somewhat separated from the rest of event handlers and methods that are specific to the page life cycle and behavior. Because page methods are required to be static (shared in Microsoft Visual Basic .NET), you can’t use the syntax of interfaces to define the contract. You have to resort to abstract base classes. |
Alternatively, you can define Web methods as inline code in the .aspx source file as follows. If you use Visual Basic, just change the type attribute to text/VB.
Note that page methods are specific to a given ASP.NET page. Only the host page can call its methods. Cross-page method calls are not supported. If they are critical for your scenario, I suggest that you move to using Web services.
Enabling Page Methods
When the code-behind class of an ASP.NET AJAX page contains WebMethod-decorated static methods, the runtime engine emits a JavaScript proxy class nearly identical to the class generated for a Web service. You use a global instance of this class to call server methods. The name of the class is hard-coded to PageMethods. We’ll return to the characteristics of the proxy class in a moment.
Note, however, that page methods are not enabled by default. In other words, the PageMethods proxy class that you would use to place remote calls is not generated unless you set the EnablePageMethods property to true in the page’s script manager:
As a side note, consider that this property was added quite late in the development cycle of ASP.NET AJAX Extensions, just before the release-to-market (RTM) version. Code ported to the RTM version may require this property to be added and set to work correctly.
Runtime Support for Page Methods
For the successful execution of a page method, the ASP.NET AJAX application must have the ScriptModule HTTP module enabled in the web.config file:
Among other things, the module intercepts the application event that follows the loading of the session state, executes the method, and then serves the response to the caller. Acquiring session state is the step that precedes the start of the page life cycle. For page method calls, therefore, there’s no page life cycle and child controls are not initialized and processed.
Why No Page Life Cycle?
In the early days of ASP.NET AJAX (when it was code-named Atlas), page methods were instance methods and required view state and form fields to be sent with every call. The sent view state was the last known good view state for the page-that is, the view state downloaded to the client. It was common for developers to expect that during the page method execution, say, a TextBox was set to the same text just typed before triggering the remote call. Because the sent view state was the last known good view state, that expectation was just impossible to meet. At the same time, a large share of developers was also complaining that the view state was being sent at all during page method calls. View state is rarely small, which served to increase the bandwidth and processing requirements for handling page methods.
As a result, starting with the beta stage, ASP.NET AJAX Extensions requires static methods and executes them just before starting the page life cycle. The page request is processed as usual until the session state is retrieved. After that, instead of going through the page life cycle, the HTTP module kicks in, executes the method via reflection, and returns.
Coded in this way, the execution of a remote page method is quite effective and nearly identical to having a local Web service up and running. The fact that static methods are used and no page life cycle is ever started means one thing to you-you can’t programmatically access page controls and their properties.
Consuming Page Methods
The collection of page methods is exposed to the JavaScript code as a class with a fixed name-PageMethods. The schema of this class is similar to the schema of proxy classes for AJAX Web services. The class lists static methods and doesn’t require any instantiation on your own. Let’s take a look at the PageMethods class.
The Proxy Class
Unlike the proxy class for Web services, the PageMethods proxy class is always generated as inline script in the body of the page it refers to. That’s a fairly obvious choice given the fixed naming convention in use; otherwise, the name of the class should be different for each page. Here’s the source code of the PageMethods class for a page with just one Web method named GetTime:
As you can see, the structure of the class is nearly identical to the proxy class of an AJAX Web service. You can define default callbacks for success and failure, user context data, path, and timeout. A singleton instance of the PageMethods class is created and all callable methods are invoked through this static instance. No instantiation whatsoever is required.
Executing Page Methods
The PageMethods proxy class has as many methods as there are Web methods in the code-behind class of the page. In the proxy class, each mapping method takes the same additional parameters you would find with a Web service method: completed callback, failed callback, and user context data. The completed callback is necessary to update the page with the results of the call. The other parameters are optional. The following code snippet shows the getTime function bound to a client event handler. The function calls a page method and leaves the methodCompleted callback the burden of updating the user interface as appropriate.
function getTime() { PageMethods.GetTime(methodCompleted); } function methodCompleted(results, context, methodName) { // Format the date-time object to a more readable string var displayString = results.format("ddd, dd MMMM yyyy"); $get("Label1").innerHTML = displayString; }
The signature of a page method callback is exactly the same as the signature of an AJAX Web service proxy. The role of the results, context, and methodName parameters is the same as described in Table 7-3.
Timeout, error handling, and user feedback are all aspects of page methods that require the same programming techniques discussed earlier for Web service calls.
Note |
From page methods, you can access session state, the ASP.NET Cache, and User objects, as well as any other intrinsic objects. You can do that using the Current property on HttpContext. The HTTP context is not specific to the page life cycle and is, instead, a piece of information that accompanies the request from the start. |
Page Methods vs. AJAX Web Services
From a programming standpoint, no difference exists between Web service methods and page methods. Performance is nearly identical. A minor difference is the fact that page methods are always emitted as inline JavaScript whereas this aspect is configurable for AJAX Web services.
Web services are publicly exposed over the Web and, as such, they’re publicly callable by SOAP-based clients. A method exposed through a Web service is visible from multiple pages; a page method, instead, is scoped to the page that defines it. On the other hand, a set of page methods saves you from the additional work of developing a Web service.
Whatever choice you make, it is extremely important that you don’t call any critical business logic from page and Web service methods. Both calls can be easily replayed by attackers and have no additional barrier against one-click and replay attacks. Normally, the view state, when spiced up with user key values, limits the range of replay attacks. As mentioned, though, there’s no view state involved with page and Web service method calls, so even this small amount of protection isn’t available for these specific cases. However, if you limit your code to calling UI-level business logic from the client, you should be fine.
Bridging External Web Services
As mentioned, for security reasons the browser is often not allowed to make calls to an endpoint server that differs in port, protocol, or server name from the current one. This constraint might be relaxed to some extent in the future, but as of this writing any call to an external endpoint can’t occur from the client unless an IFRAME block is used. ASP.NET AJAX Extensions 1.0 doesn’t natively support an HTTP executor based on IFRAMEs. There was one in the early days of Atlas, but it was removed in the beta stage. You can manage to create your own, however, using the base classes in the Microsoft AJAX library infrastructure.
So what if you want to connect to a remote endpoint (for example, the Amazon or Yahoo Web service) and bring in some external data? What if you want to make your application a mash-up? There are two approaches. One is the classic server-to-server schema where the client calls its server and has the server get in touch with the remote URL. The other is based on an upcoming feature of ASP.NET AJAX known as Futures, which currently requires an additional download. Let’s dig out more details.
Traditional Server to Server Approach
In classic ASP.NET applications, invoking a remote Web service has never been an issue. You import the Web reference to the endpoint in the project, save the WSDL of the service in the App_WebReferences special folder, and go. The ASP.NET runtime then generates the proxy class for you, including for each Web method both a synchronous and an asynchronous stub.
Invoking a Web service from a server ASP.NET page is not problematic from an implementation perspective, but it is a delicate point from a performance standpoint. The call might take a while to terminate, be it for the network latency or for the inherent complexity of the task. Making an asynchronous call to the Web service is not enough: you need it to occur from within an ASP.NET page bound to an asynchronous HTTP handler.
Digging out the details of asynchronous ASP.NET pages is beyond the purpose of this book. So I’ll limit the discussion to calling Web services synchronously. However, for more information on how to create asynchronous ASP.NET pages, check out Chapter 4 of my book Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics (Microsoft Press, 2006).
Using .NET Proxies
For a large share of .NET developers, using a proxy class is the most natural way to consume a Web service. The proxy class is created for you by Visual Studio 2005 when you add a Web reference. Created in C# or Visual Basic .NET, the proxy class mimics the interface of the remote service as prescribed in the WSDL file.
As a developer, all that you have to do is create an instance of the proxy, opt for a synchronous or asynchronous call, and invoke the appropriate proxy method. Here’s a quick example of a synchronous call:
using IntroAjax.WebServices; ... TimeServiceClient proxy = new TimeServiceProxy(); string formattedDate = proxy.GetTimeFormat("dd-mm-yyyy");
In ASP.NET, the call has to be synchronous if you need to incorporate any results in the response. For start-to-finish calls, asynchrony is not an advantage per se in ASP.NET. You get real benefits only if you can have an asynchronous handler to process the page request. In this case, in fact, the ASP.NET thread is returned to its pool until the Web service operation terminates.
Using an asynchronous method on the proxy class makes sense if the Web service operates in the fire-and-forget modality, meaning that the ASP.NET page starts the method but doesn’t wait for it to terminate.
Using Plain Web Requests
As an alternative to proxy classes, if the Web service supports the plain-old XML model (POX), you can just send requests to a URL where input data is packed in the query string according to a service-specific syntax. Here’s how to obtain a response in a synchronous manner:
WebRequest req = WebRequest.Create(url); WebResponse response = req.GetResponse(); StreamReader reader; string output = String.Empty; using (reader = new StreamReader(response.GetResponseStream())) { output = reader.ReadToEnd(); reader.Close(); response.Close(); }
The output variable contains the response as sent by the server. Parsing the response and extracting any useful information is now entirely up to you.
ASP NET AJAX Futures Bridge Files
The ASP.NET AJAX bridge technology enables developers to create programmatic gateways to Web services on the Internet. Because bridge code runs inside the caller ASP.NET AJAX application, the browser communicates with just one server. The bridge code, in turn, can interact with any Web service available on the Internet and return data to the client page. Bridge code is the chief enabling technology for building mash-ups, which are applications that combine data from multiple remote services and serve it to clients in a usable form.
Functionally speaking, the bridge technology is equivalent to making direct calls to a Web service either through a proxy or a Web request. It provides a declarative programming interface and tends to hide as many details as possible from ASP.NET developers.
Important |
The bridge technology is not included in ASP.NET AJAX Extensions 1.0. A pre-release version of it can be obtained under the terms of the Go-Live license by installing the ASP.NET AJAX Futures download from http://ajax.asp.net. The bridge technology will be part of the next version of ASP.NET slated for release in late 2007. |
Configuration and Setup
The ASP.NET AJAX gateway to a third-party Web service is an .asbx file. The file contains XML data that describes how to make the call to the Web service. The primary reason for having an intermediary is that real-world Web services might not accept parameters as individual values but require an object instead. In turn, this object might include properties that are also complex types. Therefore, constructing requests and parsing return values might require some programming effort. This is where bridge files (.asbx files) fit in.
To connect to third-party Web services from ASP.NET AJAX, you need to register the .asbx extension in Internet Information Services (IIS) and make ASP.NET handle it. To see how this is done, open the configuration dialog box in the target IIS virtual folder, select the Mappings tab, and take a look at the contents of the Add/Edit Application Extension Mapping dialog box. (See Figure 7-3.)
Figure 7-3: Registering the ASBX extension in IIS
Then you edit the mappings and bind .asbx resources to the aspnet_isapi.dll file-the ISAPI extension that handles ASP.NET calls within IIS.
To continue, you also need to register the HTTP handler for bridge files:
...
You can also move this setting to a folder-level web.config file if all the bridge-based pages live in a subfolder.
Finally, you define a build provider for .asbx files so that a wrapper class can be silently generated around the source of the bridge file:
In addition to all these changes to the web.config file, you should make sure you add a copy of the Microsoft.Web.Preview assembly to the Bin folder of your application.
The Bridge File
As mentioned, a bridge file is simply an XML file with the .asbx extension. Through a bridge file, you declare how you connect to a remote Web service and how you intend to process any return value.
Currently, a number of popular Web services support two calling protocols: SOAP and POX. SOAP is the well-known Simple Object Access Protocol and defines the XML-based structure of the messages that appear in a distributed environment exchange. SOAP is the protocol that Web services use to interact with their callers. As we’ve seen, POX stands for plain-old XML and consists of a GET command that lists all parameters on the query string. Why is POX used by a number of popular Web services, including MSN Search and Amazon? The answer can be summed up in just one word: reach. More people can access these POX-based Web services if the complexity associated with SOAP is negated. (Make no mistake, SOAP is not necessarily simple-it’s just easier to work with than predecessor protocols.)
Let’s see how to create a bridge file that uses POX to access the Amazon Web service and query for books. (The Amazon Web service, however, also exposes a SOAP-based interface from which a proxy class can be created.)
Building a Bridge File for the Amazon Web Service
The Amazon Web service allows Web site developers to programmatically interact with the Amazon catalog, search engine, shopping cart, and merchandising tools. To download the necessary software developer’s kit, go to the following URL:
- http://associates.amazon.com/exec/panama/associates/join/developer/kit.html
You need a developer token to start using the Web service, and this page facilitates that. If you need to know about the books written by a given author, you place a call to the following URL and add a number of additional parameters:
- http://xml.amazon.com/onca/xml2
Table 7-4 details some of the most frequently used parameters.
Parameter |
Description |
---|---|
t |
Indicates the tag of the query. Must be set to webservices-20. |
f |
Indicates the output format. Must be xml. |
page |
Indicates the page required. Set to 1 by default. |
sort |
Indicates the sorting required. Valid values are +salesrank, +daterank, and +titlerank (alphabetical). |
dev-t |
The developer’s token. |
mode |
Indicates the catalog to search. Must be set to books. |
type |
Indicates how much information must be packaged in the response. Feasible values are lite or heavy. |
keywordSearch |
Indicates the query string. |
In the bridge file, you create a programming interface around the capabilities of the Web service. For example, you can define a JavaScript wrapper with a Find method that accepts the keyword and returns some data. Here’s the corresponding bridge file:
In the tag, the namespace and classname attributes define the full name of the resulting JavaScript class. In the element, the type attribute indicates the component in charge of executing the remote call on behalf of your application. The serviceUrl attribute sets the URL to call.
The elements define the programming interface of the bridge class. Any methods you define here are added to the IntroAjax.AmazonPox JavaScript wrapper you will use in your ASP.NET AJAX pages. Of course, you can have as many elements as you like.
The preceding script defines a Find method that takes up to eight parameters. All the listed parameters (and only the listed parameters) will be added to the GET command that is sent to the service URL. The first six parameters are given fixed values. For the seventh argument-the dev-t parameter-the value is read from the section of the web.config file:
At this point, you’re ready to write a piece of JavaScript code that calls into the Amazon Web service and gets some information about the books written by a given author. What information? That’s the point!
Consuming Amazon Data through a Bridge
The following page adds a reference to the bridge file using the same syntax we used for local Web services. The sample page includes a text box and a button. The user will type an author name in the text box and click the button to search for that author’s books.
Find an author:
Let’s take a look at the findBooks function:
Try running this page. Then type Dino Esposito in the text box, and click the button. Figure 7-4 shows what you’ll get.
Figure 7-4: The bridge file retrieving information from the Amazon Web service
It works, but can you honestly say that you got just what you were hoping for? The returned value is a huge XML string in which all the requested information is packed according to a schema defined by Amazon developers. How would you transform this string into something usable by your users?
Transforming the Web Service Response
By default, the response of an AJAX Web service call is assumed to be a JSON data stream and is parsed to a JavaScript object. This is not necessarily true, though, when a non-AJAX Web service is invoked. Amazon, or any other third-party Web service, doesn’t know about your JSON expectations and may just return XML or text data. In the end, the data is correctly downloaded, but it’s not immediately usableby an AJAX client if it wasn’t JSON encoded. By adding a section to your bridge file, you instruct the ASP.NET AJAX infrastructure to post-process the return value and build a JavaScript object you can successfully interact with to present results to the user.
You can use a variety of transformers to post-process the return value of a Web service call. ASP.NET AJAX comes with a few built-in transformers, which are listed in Table 7-5.
Type |
Description |
---|---|
ObjectMapperBridgeTransformer |
When the return value is an object, this type maps properties in the returned object to a custom object. |
XmlBridgeTransformer |
This type runs the return data through the XmlSerializer and obtains an XML string that represents the output. |
XpathBridgeTransformer |
This type uses XPath queries to extract data from the response string and build a custom object. |
XsltBridgeTransformer |
This type applies an XSLT style sheet to the XML document that represents the return data. |
The following code demonstrates how to parse the XML string returned by Amazon using XPath queries to extract significant information. To write a transformer, you need to know a lot about the format of the response text.
Amazon responses take the following format:
... ...
...
...
...
The element indicates how many items your search selected, whereas specifies the number of pages it takes, assuming a default size for the page. Finally, an array of
blocks lists information about individual items-books, albums, and whatever. Each block contains at least the tag, which indicates the Amazon identifier for the item. For books, that’s just the ISBN code.
The following transformer processes the response into an array of simpler JavaScript objects:
The selector attribute instructs the XPath to select the specified node-in this case, Details. Next, the selectedNodes dictionary selects the node set that is rooted in the selected node. As a result, all nodes named Details are selected. The transformer creates a JavaScript dictionary that contains objects with four properties: ISBN, Title, Publisher, and CoverPicture. Each of these properties is mapped to a particular element under the nth
element.
At this point, the value passed to the method-complete callback is an array of JavaScript objects with the preceding four properties. You can now build a nice user interface, as shown in Figure 7-5.
Figure 7-5: A quick but effective example of a mash-up application using listed books from Amazon
function findBooksCompleted(results, context, methodName) { var builder = new Sys.StringBuilder(""); for(i=0; i"); builder.append(""); builder.append(""); } builder.append("
"); builder.append(results[i].ISBN); builder.append(" "); builder.append(results[i].Title); builder.append(" "); builder.append(results[i].Publisher); builder.append(" |
"); $("Output").innerHTML = builder.toString(); }
Conclusion
Of the two ASP.NET AJAX models of enhancing Web sites, partial rendering is the one with some hidden costs. Although it can still achieve a better performance than classic postbacks, partial rendering moves a lot of data around. In some worse cases, the savings in terms of markup are negligible compared to the quantity of bytes moved. On the other hand, AJAX was developed around the idea of making stateless server-side calls from the client and updating the page via the DOM.
Here’s where the second model fits in-direct client-to-server calls. No hidden costs are buried in this model. As in a classic SOAP-powered Web service call, you send in only input data required by the method being invoked and receive only the return value. Traffic is minimal, and no view state or other hidden fields (for example, event validation) are roundtripped. On the down side, remote method calls require JavaScript skills. You command the execution of the method via JavaScript, and use a JavaScript callback to incorporate the results in the page.
You need a server API to plan and execute client-to-server direct calls. How would you expose this API? As per the current version of ASP.NET AJAX Extensions, you’re not forced to define a contract (for example, an interface). However, it’s a good software practice to define the server API as an interface. How would you implement such a server API? There are two options: as a local, application-specific Web service, or through page methods. In both cases, the ASP.NET AJAX client page is enriched with a system-generated proxy class to make calling the server easy and effective.
In the context of ASP.NET AJAX Extensions 1.0, Web services are instrumental to the implementation of a server API. Beyond this, is it possible for an ASP.NET AJAX client to call into any Web service out there? Security reasons dictate that most browsers stop cross-domain and cross-site calls via script. This reality is a huge hurdle for AJAX applications. For this reason, you need a bridgehead on the server to make a server-to-server call to a remote endpoint and return data to the client. In the future, the bridge technology will provide a declarative way of linking remote services to client pages; meanwhile, you have to code it yourself as you would do today in classic ASP.NET applications.
Chapter 8 Building AJAX Applications with ASP NET
Категории