Remote Scripting in a .NET World

 

Andrew Clinick
Microsoft Corporation

November 12, 2001

View the sample, service_vb.htm
View the sample, service_jscript.htm
View the code for clsWeatherVB.asmx
View the code for clsWeatherJScript.asmx

In March 2000 I wrote an article covering Microsoft® Remote Scripting and whether it was applicable in a Web Services world. In the article, I promised to show you how to take your Remote Scripting knowledge and move it to .NET Web Services. A little later than I expected, this article will do that, and in particular, discuss how to migrate your Remote Scripting code to be a .NET Web Service, demonstrated for both Microsoft® Visual Basic® .NET and Microsoft® JScript® .NET. The migration will cover both the client and server portions of your code. To illustrate the migration, I will use the weather Remote Scripting example from the original article, and move that to .NET.

Why Use Web Services Instead of Remote Scripting?

The WebService behavior provides a number of advantages over the Remote Scripting model, notably:

  1. Uses industry standard SOAP requests to the server. Remote Scripting had its own variation on XML for the responses.
  2. Works great with ASP.NET. Remote Scripting requires ASP, which means that you can't take advantage of all the enhancements (speed, compiled languages, .NET Framework) that ASP.NET offers.
  3. Not restricted to doing HTTP GET for server requests. Remote Scripting is limited to 2 KB of data when making a request to a server. The WebService behavior uses HTTP POST, so doesn't suffer from this restriction.
  4. Allows you to take advantage of .NET functionality from within a browser.

Migrating the VBScript Server Code

The original example code used weatherservice.asp to implement the Weather Service. This was accomplished by including a .vbs source file (clsweather.vbs), which contained the implementation of the clsWeather class, and some Visual Basic Scripting Edition (VBScript) code that created an instance of the class, and then called the RSDispatch method on the Remote Scripting include, which enables calls to be made to the instance of the class.

<%@ LANGUAGE=VBSCRIPT %>
<script language="VBscript" runat="server" src="clsweather.vbs"></script>
<% 
set public_description = new clsWeather

' Call RSDispatch to use the public_description object and 
make its methods available for Remote Scripting calls
RSDispatch %>
<!--#INCLUDE FILE="../_scriptlibrary/rs.asp"-->

vbsweatherservice.asp

The implementation of the VBScript class is quite simple with just one method, getConditions, which takes a city name and returns the weather conditions. Since it's a demo, it only works for a few select cities—but I think the weather forecasting is as accurate as we get on many Web sites!

Class clsWeather
Public function getConditions(strCity)
   Select Case ucase(strCity)
      Case "LONDON"
         If month(now) <= 7 or month(now) >=9 then 
            getConditions = "overcast"
         elseif month(now) = 8 then
            GetConditions = "partly overcast"
         End if
      Case "SEATTLE"
         If (month(now) = 7) and (day(now) = 4) then
            GetConditions = "torrential rain"
         Else
            GetConditions = "rain"
         End if
      Case "LA"
         GetConditions = "smoggy"
      Case "PHOENIX"
         GetConditions = "damn hot"
      Case Else
         GetConditions = "partly cloudy with a chance of showers"
   End select
end function
End Class

Step 1. Create a Web Service File

The .NET Framework provides a specific type of file for defining and implementing Web Services: .asmx. The first thing required to migrate a Remote Scripting ASP page is to create an .asmx file. Every .asmx page includes a WebService directive, which is used to tell ASP.NET in what language the Web Service is written and which class to use for the implementation. The class element on the WebService directive is equivalent to the public_description variable in the original Remote Scripting ASP page.

<%@ WebService Language="VB" Class="clsWeather" %>

Step 2. Add the Existing VBScript

Once the Web Service has been defined in the .asmx file, the implementation of the class needs to be added to the file. Since the original ASP page used a VBScript class (as do pretty much all VBScript Remote Scripting files), I just copied and pasted the script into the .asmx page. As many of you will already be aware, there are a number of incompatibilities between VBScript and Visual Basic .NET, but luckily the class syntax is very similar (Class ... end class). Since my original script is reasonably simple, it will run in Visual Basic .NET without any modifications to the script's logic. However, there are a few things that need to be added to the code in order to get it to compile—specifically importing some namespaces.

The first thing required is to import the Microsoft.VisualBasic namespace to ensure that functions implemented in the Visual Basic runtime, such as UCase, are usable in the script code. Importing the System namespace is pretty much a prerequisite for most .NET applications, so I add that just to make life a little easier. Finally, since the code is going to be implementing a Web Service, the code imports System.Web.Services.

Imports Microsoft.VisualBasic
Imports System
Imports System.Web.Services

Getting the script code to compile is a start, but the Web Service isn't quite ready to go. The ASP.NET processor needs to be able to determine which methods in the clsWeather class are to be exposed on the Web Service. This is achieved by a custom attribute, WebMethod, which is added to every method to be exposed by the Web Service. This provides more flexibility than the Remote Scripting mechanism, because every public method in the class exposed by Remote Scripting was callable through Remote Scripting. Adding a custom attribute to a method in Visual Basic is pretty simple; just add it to the beginning of the method. So in this case it would be:

<WebMethod()> Public function getConditions(strCity)

The VBScript implementation of the getConditions method didn't type the strCity argument (not surprising since VBScript doesn't support types). This doesn't mean that the Web Service won't be usable, since it would just be typed as object. Nevertheless, providing a type will make it more robust, since this will allow for the Web Service to do type checking when a call is made to the function. To take advantage of this, I typed the argument and the function as a string:

<WebMethod()> Public function getConditions(strCity as string) as string

Step 3. Save and Run the .asmx File

The .asmx file is now ready to be saved to the server and tested. The great thing about Web Services with ASP.NET is that each Web Service will create an automatic test Web page, which provides a good test harness for checking out your code. One of the most common requests we received from Remote Scripting customers (other than supporting HTTP Post!) was for a simple way to test out their code. To get to the page, just type in the URL of the .asmx file in your browser, and it brings up the default test page listing all methods available on the Web Service.

Figure 1. Web Service test page

Clicking on the getConditions link brings up a page that lists all the arguments on the method. This is where the typing comes into it's own, since the ASP.NET Web Service processor reads the type information for the argument, and provides a simple html form for you to test out the service.

Figure 2. Test form for getConditions

When you click on the invoke button, a call to the Web Service (through HTTP GET, so there are data size restrictions) and the XML returned is shown in Internet Explorer.

Figure 3. XML returned

Migrating the JScript Version

Taking the existing JScript Remote Scripting implementation is very similar to the VBScript version, but there are some JScript specifics. The main change is that JScript prior to JScript .NET didn't have the notion of classes, so the original Remote Scripting ASP page had a constructor function that was exposed by the public_description variable on the page.

<%@ LANGUAGE=JSCRIPT %>
<script language="JScript" runat="server" src="clsweather.js"></script>
<% 
public_description = new clsWeather

' Call RSDispatch to use the public_description object 
and make its methods available for Remote Scripting calls
RSDispatch() %>
<!--#INCLUDE FILE="../_scriptlibrary/rs.asp"-->

jsweatherservice.asp

The constructor function sets this.getConditions to be a pointer to the getConditions function, so that when a new instance of clsWeather is created, it will have the getConditions method exposed. The implementation of the getConditions function is as simple as the VBScript version.

function clsWeather() {
   this.getConditions = GetCondition
function getCondition(strCity) {
   var now = new Date();
   switch (strCity.toUpperCase()) {
      case "LONDON":
         if (now.getMonth() <= 6||now.getMonth() >=8){
            return "overcast"
         }
         if (now.getMonth() == 7) {
            return "partly overcast"
         }
         break;
      case "SEATTLE":
         if (now.getMonth() == 6 && now.getDay()==4){
            return "torrential rain"
         }
         else {
            return "rain"
         }
         break;
      case "LA":
         return "smoggy"
         break;
      case "PHOENIX":
         return "hot"
         break;
      default:
         return "partly cloudy with a chance of showers"
   }
}
}

Step 1. Create a Web Service File

This is almost identical to the Visual Basic step, only the language name changes to Jscript:

<%@ WebService Language="JScript" Class="clsWeather" %>

Step 2. Add the Existing JScript

Since the Web Service is going to be running in JScript .NET, and will be accessing the Web Services functionality provided by the .NET Framework, the first thing that needs to be added to the .asmx file is a set of import statements to import the required namespaces into the code. JScript uses the import statement to specify namespaces, so:

import System
import System.Web.Services

In order to use the JScript code in a Web Service, it has to be in a class that the ASP.NET processor can create an instance of. Migrating from a constructor function to a class isn't hugely difficult, since the vast majority of the code doesn't change. It only requires changing the constructor function to a class.

function clsWeather() {
   this.getConditions = getCondition
function getCondition(strCity) {
becomes:
class clsWeather {
   
function getConditions(strCity) {

The function getCondition is automatically created as a public function within the class clsWeather, which is in turn used by the ASP.NET Web Services processor when it reads the WebService directive at the top of the file.

All that remains now is to mark the getConditions method as a Web method by using the WebMethod attribute. JScript doesn't require any specific escape characters around the attribute; it just needs to be the first entry on the line. The WebMethod attribute has a description argument that becomes the documentation element for the method in the Web Service Description Language (WSDL) used for all Web Services calls. The good thing about this is that the test harness provided by ASP.NET shows the description for each method. I used the description argument in the JScript sample.

WebMethod(Description="return the weather conditions for a city")
function getConditions(strCity) {

Once the class change has been made, the Web Service will run, but as in the Visual Basic case, adding a type to the strCity argument on the getConditions function will provide a more robust solution. Adding types in JScript is pretty simple; just add a colon and then a typename.

function getConditions(strCity : String) {

Step 3. Save and Run the .asmx File

This works in exactly the same way as the Visual Basic version, so I won't bore you with more screen shots.

Using the Web Service from a Web Page

One of the major benefits of Remote Scripting is being able to call server functionality on the client without having to refresh the Web page. The existing remote script client doesn't understand the Web Services XML, but all is not lost since WebService behavior works in a very similar manner, and provides the elusive ability to use HTTP Post for calling server methods.

A feature common to both Remote Scripting and the WebService behavior is the ability to call a method asynchronously or synchronously. Choosing the right mechanism for your application is important, since it can have a considerable impact on the user's experience. Calling a Web Service could take some time, so if you call it synchronously it will lock up the browser, which could annoy your users no end. That's not to say that calling synchronously is wrong, but it's important to think about the impact before using it.

Calling the getConditions Method

The original example from March 2000 has a simple Web page that takes the value of a text box, calls the getConditions method, and updates a Div with the result. It did this by using the RSGetASPObject function from the Remote Scripting client-side JScript to get a client-side proxy object for the weatherservice.asp page, and by then calling the getConditions method. The innerHTML of the div is then set to be the return value of the method.

svcWeather = RSGetASPObject("weatherservice.asp")
divWeather.innerHTML = svcWeather.getConditions(txtCity.value).return_value

The downside to this approach is that it's synchronous. Instead, the page could have used RSExecute with the name of the method to be called, the arguments, and then the name of the function to be run when the call to the server returns.

varTest = RSExecute("weatherservice.asp","getConditions",
 txtCity.value,getConditionsCallBack)

Using the WebService behavior allows you to achieve very similar results. To add the WebService behavior to your Web page, you need to assign the behavior to an element on the page. In the example page that comes with this article, I added a div and assigned the behavior to that. The behavior is implemented as an HTML Component (.htc), so you can install it on your Web server.

<div id=service style="BEHAVIOR: url(webservice.htc)"></div>

The equivalent of RSGetASPObject in the WebService behavior is the UseService function. UseService allows you to create an instance of a client-side object that provides access to all the methods on the supplied Web Service. For example service.useService("clsWeather.asmx?WSDL","svcWeather")will create a property, svcWeather, on the service object, and assign to it a client-side object based on the clsWeather.asmx Web Service. Once the object has been created, calling the remote method is achieved by calling the callService method on the returned object with the name of the callback function, the method on the Web Service, and arguments. When the getConditions method returns, the showWeather function is called by the behavior, which sets the innerHTML to be the value of the return result.

service.useService("clsWeather.asmx?WSDL","svcWeather")
   // Call the getConditions method on the svcWeather web service
   iCallID = service.svcWeather.callService(showWeather,"getConditions",txtCity.value)
function showWeather(result)
{
       // Show the conditions
        divWeather.innerHTML = result.value
}

Summary

The onset of Web Services is going to have a profound effect on how we develop software from now on. I hope that this article has shown you that with a little work, you can easily migrate your existing Remote Scripting applications to take advantage of Web Services and the .NET platform. I recommend that you look further into the WebService behavior on MSDN, where you'll find full documentation with some great examples. As always, we appreciate and encourage your feedback, so feel free to contact us at msscript@microsoft.com.

 

Scripting Clinic

Andrew Clinick is a senior program manager in the Microsoft Programmability group, so chances are, if there's script involved, he's probably had something to do with it.