cutting@microsoft.com Download the code (14KB) |
Dino Esposito |
Remote Object Scripting |
Remote Objects
Today the word "object" brings to mind visions of COM. However, an object can be something much simpler than the standard COM object. For the moment, let's consider an object to be something that exposes a programming interface. To migrate the client/server model to the Web you need the ability to call functions defined and implemented by objects located on the Web server. Put another way, this means that your goal is to execute code on the server and see the results through the client.
Figure 1: An Inelegant Solution |
Figure 1 shows a typical workarounda schema that you've probably already implemented countless times. The current page submits a request that specifies an ASP page. The referenced ASP page is processed on the server and produces a brand new HTML page using the input parameters and possibly some business components running on the server and Microsoft® Internet Information Services (IIS). From the user's point of view, you passed from a default.htm page to a new page.asp page. You could just as easily have gone from page.asp to a new page.asp. Even if the names of the pages are the same, they are generated separately. In this scenario, the remote object the browser is talking to actually consists of the whole ASP page. The smallest unit you can control from the script code in this example is the page itself, though by specifying parameters you can narrow this to pieces of code within the page. This inelegant approach is a direct consequence of the stateless HTTP protocol. Is there a better way to do this with HTTP as the underlying protocol? Figure 2 depicts a more reasonable scenario, where the client code invokes a function on the server and data is returned to the client, which uses the data to update its own user interface. The big difference is that now the user doesn't see a complete page replacement, but is simply shown an updated page. Under the hood, the client asks the server to return some data, waits for it to arrive, and then uses it to refresh the user interface, just like in the traditional client/server model. |
Figure 2: Using Remote Objects |
However, working over the Web requires two types of solutions: one for remote calls and one for refreshing the user interface. The latter affects only the client side of the system; the former applies to the server as well as the client. In Figure 2, the remote object is a piece of code (typically, a function) defined within an ASP page. In general, however, you can rely on a variety of technologies and solutions to access remote objects using HTTP. As shown in Figure 3, different solutions apply to different types of remote objects, exploit different transportation layers for the connection, and set different requirements for the browser. Figure 3 lists the most commonly used techniques to get a stream of bytes from a remote object without leaving the current page. Of course, you can write your own specialized COM or Java language components, but I won't discuss that here. In the coming months, I will examine and compare each of these techniques. In this installment, the focus is Remote Scripting (RS).
What is Remote Scripting?
|
Figure 4: The Remote Scripting Architecture |
Figure 4 depicts the RS architecture, and Figure 5 summarizes the RS functions that enable you to issue remote calls. To start, you must initialize the RS infrastructure through the RSEnableRemoteScripting function. As explained in Figure 3, RS uses a Java language applet (rsproxy.class) to send HTTP requests to the server and, of course, this applet must be instantiated. You don't need to know the details since all the necessary operations are carried out within RSEnableRemoteScripting; just add a call to this function. The typical header for an RS-enabled Web page looks like this: |
|
The source code for RSEnableRemoteScripting (see rs.htm, which you can find in the c:\inetpub\wwwroot\
_scriptlibrary directory after installing Remote Scripting 1.0 from http://msdn.microsoft.com/scripting) simply uses document.write to add an <APPLET> tag to your page. You need this to work with almost any browser, so make sure you call RSEnableRemoteScripting at the top of the body. Despite the .htm extension, rs.htm is a script-only page implemented in JavaScript. More precisely, the language is a subset of JavaScript which complies with the ECMAScript standard. When you're looking for compatibility with as many browsers as possible, use JavaScript as the script language. Now you can call remote functions on remote objects. A remote object as defined here is just a JavaScript object contained within an ASP page with a very specific name. Through the methods it exposes, this object defines the programming interface you can call remotely. |
|
The previous code snippet describes the interface of the remote object. RemoteObject is a JavaScript object with a single method called FirstMethod. The source code for this consists of the DoFirstMethod function. A remote call will reference the ASP page containing this code, specifying the name of the method with all the required parameters. Notice that public_description (all lowercase!) is a special variable name that the RS stub uses to locate the programming interface of the object. The stub code in any RS-enabled ASP page looks like this: |
|
RSDispatch is a function defined in rs.asp. Its role is to get the information coming in from the client, locate the method to call locally, execute that method, and then pack the return value into a custom structure, called a Call Object (CO), as the response to the client. Everything is automatically converted to a string and rebuilt by the proxy. Remote Scripting can get data from a remote source without leaving the current browser's page. It lets you execute code on the server in a sort of parallel conversation. The browser doesn't carry it out itself, but relies on a Java language applet or a COM component. The functions you can run on the server from your client page are those exposed by the ASP page through the public_description object. So to exploit RS, start by writing ASP pages that expose an RS-compliant public interface. Next, RS-enable your client pages and use RSExecute or RSGetASPObject to issue calls. Figure 6 demonstrates how to do this.
Asynchronous Calls and Callbacks
|
|
where co is an argument that evaluates to a Call Objectthe return value of any RS operation. The callback executes on the client, and the RS proxy is responsible for calling it when appropriate. In programming, a callback function always has a fixed prototype and can accept arguments that refer to items to work on. This reference is called the context where the callback will work. The RSExecute function is the principal means for issuing RS calls. It lets you run synchronous as well as asynchronous calls. If you specify a callback function, then the call is asynchronous; otherwise, it's synchronous. Figure 7 summarizes the various arguments of RSExecute. Notice that the number of arguments can vary depending on the requirements of the ASP method. The parameters for the method consist of all the arguments on the command line between the third position (the first two, AspPage and MethodName, are mandatory) and the first successive parameter whose type is "function". The first argument of this type is the user interface callback. The second is the error callback. The final argument is the context, and can be a string or an object. This information is passed to both types of callbacks. A user interface callback function provides you with a chance to update the page when the remote method terminates its execution. The return_value field (see my April 1998 column on Remote Scripting for more details about this) contains the method returned, while the field status denotes the success or failure of the whole operation. The context string is passed through the field context. The context value can be any information you want the callbacks to receive before they execute, and is not limited to strings. For example, it can be the ID of the page element to update or the element itself. The code you use to update the page depends on the type of context argument. For example, if the context is a string with the ID of the page element (say, named errLog), |
|
then use code like this in the error callback: |
|
This statement sets the property value of the page element whose ID is the content of the context string. Since the Call Object co contains just the ID of the object, I need to resort to the parent window object. Alternatively, if you pass an object as the context argument like this |
|
then you can use a more intuitive approach: |
|
An error callback is useful for getting rid of those nasty message boxes that the RS runtime displays when an error occurs. Basically, an error callback is an RS exception handler that's triggered when an error occurs. The description of the error can be found in the field message of the Call Object. Figure 8 is an implementation of the code in Figure 6. If there are errors, the description will go into a log panel. |
Figure 8: Remote Scripting in Action |
Function-based versus Object-based
|
|
Notice that with JavaScript the function names are case-
sensitive. If you start getting strange errors, you should first check the case of your RS function names.
The Good and The Bad
Cross-browser Features
RS on Netscape's Browsers
|
Figure 10: A Framed Page in a Netscape Browser |
Figure 10 displays a page in a Netscape browser where frames were used to simulate DHTML. The schema follows: |
|
The topmost frame is responsible for collecting information, while the middle one displays it, and the bottom frame contains the error messages. The key line for refreshing the third frame (called log) is: |
|
This is called from within the error callback. |
Figure 11: Asynchronous RS under Internet Explorer 5.0... |
Figure 12: ...and under Communicator 4.5 |
Figures 11 and 12 show the same RS page performing asynchronous calls under Internet Explorer 5.0 and Communicator 4.5, respectively. The full source code for the examples in this column is available, as always, from the link at the top of this article. This month's package contains the two RS projects discussed so far.
News from the Real World
|
|
To export a recordset as an array, you just create an array of arrays. This is not exactly a multidimensional array, but it looks like one. Since RS requires an ASP-compliant Web server, chances are that you have ADO installed there. But you can't expect the same on the client, so you need to convert the recordset into an array. Each field is identified by a column number, and the first element of the array contains the names of the columns. The code might look like this: |
|
where FIRSTNAME and the other names are appropriate constants matching the ordinal position of the column. This is better than using a string, but is still inelegant. In my next column, I'll discuss better options for moving recordsets between the client and the server. There is a common misconception that the amount of data that Remote Scripting can send back and forth is limited to 1KB. This is not correct. There is a limit on the quantity of data that RS can send from the client to the server, since RS does an HTTP GET, not a POST. GET is limited to 2KB of data, which is the limit for the data that RS can send from the client to the server. However, there's no limit to the amount of data that can be sent back from the server to the client.
Final Thoughts
|
From the January 2000 issue of Microsoft Internet Developer.