This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND

Cutting Edge
cutting@microsoft.com       Download the code (14KB)
Dino Esposito
Remote Object Scripting
T
here is no doubt that the Internet has changed the way you work and the type of applications you write. Until a few years ago, the typical architecture for corporate applications was based on the client/server model—a system with two actors playing their roles at the two ends of the network. Today with Windows® DNA 2000 you can employ three or more tiers. Besides the number of tiers, the biggest difference between a client/server metaphor and a Web-based approach is in the notion of state. A client/server architecture is inherently stateful—that is, it retains internal information from one method call to the next. A Web-based architecture is built on top of the HTTP protocol, which is stateless; it does not retain internal information between method calls. Thus, in migrating an application from client/server to the Web, you're trying to map a stateful protocol to a stateless one.
      Codewise, this means that all the requests and responses going back and forth between the browser and the Web server are separate, standalone pieces of conversation with no permanent relationship between them. In a nutshell, the page that issues the call and the page showing the results are two different pages, even though they have the same name.
      Writing stateless code is certainly not harder than writing any other sort of code, once you've understood a few general rules. What's important to remember when you're targeting the Web is that there is no natural way to synchronize the client's request and the server's response. In the client/server model, the client waits for a stream of data that the server is preparing. Then, when the client receives the data, it updates its own user interface to reflect the results. Until recently, to obtain this same effect over the Web you needed to resort to some tricks. Fortunately, today you can rely on a variety of technologies that provide help at different levels. Starting this month, I'll examine and compare the various options, giving you a picture of what's going on in the Web arena. In this column I'll focus on remote scripting capabilities.

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: An Inelegant Solution

       Figure 1 shows a typical workaround—a 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
      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?
      Loyal readers of MIND and Cutting Edge will remember that I first covered RS back in the April 1998 issue. Actually, not much has changed since then. The Remote Scripting package has been released (check out http://msdn.microsoft.com/scripting for late-breaking news), has undergone a regular bug-fix update and, more importantly, has been incorporated into Visual InterDev® 6.0, morphing into the PageObject object. For details about this component, check out Ken Spencer's Beyond the Browser columns in the December 1998 and January 1999 issues of MIND.
      The architecture of RS is still the same as I described it almost two years ago. But two years of practical experience can help clarify a number of obscure points. After a quick tour of the RS architecture and programming interface, I'll concentrate on the following points:

  • The concept behind the RS programming model
  • Callbacks and asynchronous calls
  • The cross-browser nature of RS
  • How to get it to work with Netscape's browsers
  • The inner relationship between Dynamic HTML (DHTML) and RS
  • Some tricks and misconceptions from real-world use
  • A quick tour of the RS architecture
      To enable RS in your Web applications, you need to include the RS library in the client page that issues remote calls. This library is contained in a file called rs.htm that you may place in any directory on your Web server. Typically, the directory is named _ScriptLibrary, but feel free to change it. This library, the client side of the RS, defines the whole RS proxy component, and a small programming interface that allows the page script code to talk to it.
Figure 4: The Remote Scripting Architecture
      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:

 <BODY>
 <SCRIPT language="JavaScript" src="http://dino/_ScriptLibrary/rs.htm">
 </SCRIPT>
 <SCRIPT language="JavaScript">
 RSEnableRemoteScripting("http://dino/_ScriptLibrary");
 </SCRIPT>
      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.

 function RemoteObject() { 
     this.FirstMethod = DoFirstMethod;
 }
 public_description = new RemoteObject();
 
 function DoFirstMethod {
    return "Hello";
 }
      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 %>
 <SCRIPT RUNAT="SERVER" language="JavaScript">
 <!--‡INCLUDE VIRTUAL="/_ScriptLibrary/RS.ASP"-->
      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
      There are two ways to invoke a remote object through Remote Scripting: synchronously and asynchronously. The asynchronous approach is particularly useful because it doesn't stop the user from working within the page while an operation is in progress.
      An asynchronous mechanism needs a callback function to let the page know about the success or failure of the operation. A callback function is a script function with a prototype like this


 function MyCallback(co)
where co is an argument that evaluates to a Call Object—the 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),

 RSExecute(aspPage, "MyMethod", param, 
           refreshPage, errHandler, 
           "errLog");
then use code like this in the error callback:

 window[co.context].value = co.message
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

 RSExecute(aspPage, "MyMethod", param, 
           refreshPage, errHandler, errLog);
then you can use a more intuitive approach:

 co.context.value = co.message
      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
      Figure 8: Remote Scripting in Action

Function-based versus Object-based
      RSExecute is just one way of issuing remote calls. RSGetASPObject is another. The difference between the two is purely stylistic. RSExecute supports a function-based syntax, while RSGetASPObject returns a client-side object with the same programming interface as the public_description object in the remote ASP page.
      The RS proxy translates each call to this object into a remote call to the corresponding method on the remote page. You never have a local copy of the ASP object—just a mirror object. Each call still traverses the network and runs statelessly on the server. The approach you choose is a matter of personal preference.
      To be picky, a function-based approach is slightly more efficient because it requires less overhead. The code shown previously can be rewritten this way using RSGetASPObject:


 function execCall()
 {
     errLog.value = "";
     obj = RSGetASPObject(serverURL+pageURL);
     obj.GetEmployeeInfo(txtEmpName.value, 
         refreshPage, errHandler, errLog);
 }
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
      Figure 9 shows an example of an ASP remote page. Now that you've seen how to create an RS-based application, let's take a few moments to understand the big picture. The beauty of RS is that it requires only that the browser supports JavaScript and ECMAScript, along with a Web server that provides ASP support. This means that RS will also work with the most recent versions of the Netscape browsers (more on this subject later).
      RS requires you to alter the structure of pages, including external files, and to write special ASP pages as server objects. It is 100 percent script-based and has nothing to do with COM. Because of this, RS can be used successfully in both intranet and Internet applications.
      A typical question that arises when talking about RS is, "How is this different from using hidden frames?" The principle is much the same; the big difference is that RS delivers ready-to-use data for your client-side code to display. This is important if you use DHTML to refresh the page. With a hidden frame, you still have to extract the data, even if it's just a value of 0 or 1. The RS infrastructure saves you from all this work. RS also lets you write object-based code, which is easier to read and maintain. To the user there's no difference between using hidden frames and RS.
      Similarly, using the Tabular Data Control (TDC) to ask an ASP page to return a tokenized string (chunks of text separated by a delimiter) works the same way as RS. But RS requires you to initialize the Java virtual machine (VM), which can be time-consuming when first invoked. The TDC is an ActiveX® control, and as such it requires special support from the browser—which is not always available.

Cross-browser Features
      There's a synergy between RS and DHTML. Why would you bother storing the data on the client if you can't update the page on the fly using DHTML? To date, only Microsoft Internet Explorer 4.0 and above fully support DHTML and let you update the page as a client/server application would. Can you use RS without DHTML? The answer is yes, even though you need DHTML to exploit RS to its fullest potential. RS is great for tasks that you execute on the server asynchronously, such as data validation against a database. RS lets you invoke a method on the server that performs all the checks while the user is still working on the page. Another example is batch updates, which can be issued asynchronously through RS. In general, if you can exploit DHTML, then RS is great both synchronously and asynchronously; otherwise, it's most useful working in the background.
      Since browser requirements for running RS are limited to Java and ECMAScript support, Netscape Communicator works fine. This is important since it means you can write Web solutions that work in a browser-independent fashion. Communicator doesn't implement the Document Object Model the same way Internet Explorer does but by using frames you can still take advantage of asynchronous RS calls. The ASP page behind a single frame can easily reference all the code you want to run from the server. This significantly reduces the impact of synchronous RS, but not asynchronous.
      RS is particularly useful if you have DHTML behind it, but it can also play a primary role when you're using frames, or simply need to do remote field validation without changing the page.

RS on Netscape's Browsers
      In terms of pure technology, there's nothing to keep you from using RS with Netscape's browsers. In practice, though, you have to cope with the less forgiving HTML syntax enforcement. While writing cross-browser code is a topic beyond the scope of this column, a few hints are in order.
      First, use the <FORM> tag if you want Netscape to correctly display all your textboxes. In addition, be careful to remove all lines of code using the DHTML object model and adjust the cascading style sheets properly. When coding, remember to use short 8.3 folder names or, better yet, apply the JavaScript escape function to the names of the folders you're using to reference the ASP pages, making sure there are no special characters left in the names.

Figure 10: A Framed Page in a Netscape Browser
      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:

 <FRAMESET rows="50%,35%,15%">
   <FRAME name=main src="input.htm" NORESIZE> 
   <FRAME name=info src="info.asp"  NORESIZE> 
   <FRAME name=log  src="log.asp"   NORESIZE> 
 </FRAMESET>
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:

 top.log.location = "log.asp?info=" +                    
 escape(co.message); 
This is called from within the error callback.
Figure 11: Asynchronous RS under Internet Explorer 5.0...
      Figure 11: Asynchronous RS under Internet Explorer 5.0...

Figure 12: ...and under Communicator 4.5
      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
      Although this is only a hunch, I suspect that RS is more commonly employed today in intranet projects than Internet applications, despite the fact that RS can be used across browsers. Perhaps this is due to the added power available when using RS with DHTML.
      By using RS in real-world projects, I've learned some lessons regarding cross-browser support that I'd like to share with you. RS works fine with Internet Explorer 3.x and higher and Netscape Navigator 3.x and higher, but only on Win32® platforms. RS doesn't work with browsers running on platforms where the Java VM is not scriptable. Thus RS doesn't work with Macintosh or Win16 browsers, but it does work with HotJava under Solaris and Netscape for Linux.
      As mentioned earlier, RS relies on a Java language applet to implement HTTP communication. The RS proxy layer, which is coded in the rs.htm file, uses ECMAScript to tell this Java language applet to do its work. If the Java VM doesn't support being scripted through ECMAScript, RS cannot work. This limitation is specific to the Macintosh and Win16 VMs.
      In RS, there is no notion of COM; everything is based on the features of the ECMAScript language. It's worth noting that the JScript ActiveXObject object is not ECMAScript-compliant. This causes a couple of nasty problems.
      First, there's no way to return ActiveX Data Object (ADO) recordsets as-is. Everything that you return from a method in a remotely scripted ASP page is marshaled by the RS server stub and returned to the corresponding client-side proxy. The stub automatically detects the type of the data you're returning and decides whether it is a simple type (a string) or a complex type that needs to be serialized and then rebuilt by the proxy. This applies to arrays and objects in particular.
      The stub doesn't understand recordsets because they are COM objects. It only understands ECMAScript objects. There are two ways to return a recordset: convert it to a string using the GetString method or convert it to an array that can be easily serialized (see Figure 13). The Recordset2Array function is part of a file you include in the server-side ASP page:


 <!--‡INCLUDE VIRTUAL="/_ScriptLibrary/RS2ARRAY.ASP"-->
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:

 a = co.return_value; 
 empName.innerText = a[1][EMPLOYEE_ID] + " - " + 
                    a[1][TITLEOFCOURTESY] + " " + 
                    a[1][FIRSTNAME] + " " + 
                    a[1][LASTNAME];
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
      Remote Scripting works with both Microsoft and Netscape browsers and qualifies as a cross-browser solution for scripting remote objects. A method can return raw strings and arrays, as well as XML strings. However, RS is not the only technology you can use to grab information from the server without leaving the current page. Depending on the data you need to retrieve, you might consider better and more efficient approaches. For example, you can use the XmlHttpRequest object for XML strings, Remote Data Services, or data binding for recordsets. In a sense, RS is the lowest level and most portable solution.
      In my next column, I'll compare RS with other technologies for accessing remote objects from the client side. Other articles about RS are available from http://www.asptoday.com and from the MSDN Online site, http://msdn.microsoft.com/scripting/ remotescripting/default.htm.

  From the January 2000 issue of Microsoft Internet Developer.