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

Dino Esposito

ASP and Windows Script Components

Download the code (5KB)

O
ne of the great innovations in Web development that has contributed to the evolution of Internet and intranet applications is ASP. ASP moved many tasks to the server, taking advantage of its sophisticated development resources and enabling the use of even thinner clients that need to understand only the simplest HTML.
      On the other hand, ASP is just a mix of HTML and script code blocks that a preprocessor expands, sending the output back to the browser. This means that as soon as the size of your application exceeds a certain limit, ASP becomes insufficient to manage it. You might think of ASP not as a real programming language, rather as a special, professional glue you can use to put together relatively small objects. Just as you wouldn't use only glue to build a house, you might not want to use pure ASP code to assemble a whole Web application.
      As a result, ASP lends itself very well to use as a sort of hosting environment for specialized components capable of interacting with the ASP object model and, subsequently, the browser. In other words, you write components to access data and prepare the parametric parts of your pages. Then you use the ASP mix of HTML and script blocks to put it all together. ASP programmers really need just two things: an integrated development environment (IDE) and tools to write COM components. IDEs like Visual InterDev® let you abstract the ASP project, presenting the various pages as specialized child elements of a unique parent container. Gone are the days when Notepad and ASP scripts were enough to create a Web project.

Design-time Controls and COM Components

      The first custom-designed server-side components were the ActiveX® design-time controls (DTCs). DTCs work like regular ActiveX controls, and are hosted by some of the IDEs you can use to produce ASP or HTML pages. DTCs are never downloaded to the client and they're completely invisible to the browser. They have no user interface except for the property pages that let you set the properties at design time. You'll find more information in the SDK Documentation area of the MSDN™ library (http://msdn.microsoft.com/library). Also, DTCs were discussed in the June and October 1997 issues of MIND.
      A DTC is really nothing more than a macro that the host environment (such as Visual InterDev) uses to insert parametric text (like a table of records) on demand. You can create ASP or HTML pages using DTCs, but once you save the page there's no further interaction between the control and the page; now there's only text in the spot where you placed a DTC.
      Recently, another generation of server-side, ASP-specific components have taken root. They are called COM components, and are nothing more than COM Automation objects. Basically, they leverage the idea behind DTCs, but with a significant difference: a COM component is an active part of the ASP page and can be seen as a runtime (as opposed to a design-time) control. As with DTCs, they expose methods and properties via COM Automation and are fully programmable. Unlike DTCs, COM components publish their output to the ASP transfer buffer and interact directly with the ASP machinery through the ASP object model. You don't need special tools to create ASP pages using COM components, and these components don't require special COM interfaces such as IActiveDesigner to interact with the host environment.
      A COM component is a COM object that can access the ASP object model. This means that a COM component can read input parameters through the intrinsic Request object and send text back to the browser via Response. It can also manage state through the Session object and access the application's settings using Application.
      Employing COM components has two widely recognized advantages. First, you end up with a layered application that has separate tiers for presentation, business logic, and data access. Second, you improve overall performance by relying on compiled components rather than interpreted pieces of script code that sooner or later become hard to read and extricate.
      You can write COM components in any of the environments supported by Visual Studio® 6.0, including Visual Basic®, Visual C++®, and Visual J++®. If you're using Visual C++ 6.0, there's a ready-to-use ATL template (see Figure 1). With Visual Basic, you reference the ASP object model from asp.dll.

Figure 1: ATL Template in Visual C++ 6.0
      Figure 1: ATL Template in Visual C++ 6.0

      At this point you might be thinking that many ASP-based applications will eventually migrate toward a collection of interoperating objects that the ASP script code simply glues together. However, you don't want to just move all the code from an ASP application into a COM object. Writing a COM object is always a more difficult proposition because it requires more programming experience.
      The point is, even if you're writing a COM component because you need to access the full Win32® API or you need the highest performance possible, chances are that you still have a lot of boilerplate script code waiting to be reorganized and, perhaps, componentized.

COM Components in VBScript

      There's a high probability that you'll need to accomplish the same task in several different ASP pages. The easiest way to solve this problem is an old favorite: cutting and pasting. Although this solution is inherently inelegant, it is far less maintainable than building a simple parametric component. For example, think of a set of validation routines or reusable procedures that manipulate arrays or dates. Putting all of them together in a reusable, object-oriented manner could significantly increase your productivity. But what if you have to write them in Visual Basic or Visual C++? I've seen lots of developers give up and resort to cut-and-paste or server-side includes (SSIs). Now, using Windows® Script Components (WSC), formerly known as server scriptlets or just scriptlets, you can write real COM objects with scripting code.
      I've covered the basics of the scriptlets technology in previous columns (MIND, May 1998 and MSDN News, November/December 1998). So here I'll just discuss the most recent changes to the syntax used in WSC files. In addition, I'll focus on the script-based components you can write to work on ASP pages.

Windows Script Component Basics

      A WSC is a text file written to follow a specific XML schema. Each WSC file contains two main blocks of information: a descriptive section and a script section. The descriptive section defines the programming interface of the component. Here you specify the registration information (CLSID, progID, and version number) and the full set of exposed interfaces. By default, a WSC exposes the IDispatch interface for automation; the programmer is simply required to specify the prototype of the various methods, properties, and events. Figure 2 contains a bare-bones WSC component. The following is the script code to call it:


 Dim wsc
 Set wsc = CreateObject("HelloWorld.WSC")
 wsc.Text = "Hi all"
 wsc.Welcome
As you can see, there's nothing in this snippet that indicates you're not working with a regular COM Automation object.
      The XML tags that form a WSC component are listed in Figure 3. In the <registration> block, the only information really needed is either the progID or the CLSID. You are free to choose the option you like better, but you absolutely must specify at least one of them. If you omit the CLSID, then a new one will be generated automatically during the registration process.
      The <public> tag gathers all the methods, properties, and events that the component exposes. As its name suggests, this section lists everything an external client can call or interact with. In the following code snippet, you can see the public section of a component that executes a query against a connection and returns an HTML string reflecting the required view:

 <public>
     <method name="Execute">
         <parameter name="sqlCommand"/>
         <parameter name="maxRecords"/>
     </method>
    <property name="Connection" />
    <property name="ViewMode">
       <get internalname="get_ViewMode" />
       <put internalname="put_ViewMode" />
    </property>
    <event name="QueryDone">
       <parameter name="recordCount" />
    </event>
 <public>
      The Connection property is implemented like a public variable. This property is read/write and its content is not validated. In contrast, reading and writing for the ViewMode property are governed by two internal routines, get_ViewMode and put_ViewMode. They have the following structure:

   function get_ViewMode()
       get_ViewMode = ViewMode
   end function
 
   function put_ViewMode (newValue)
       ViewMode = newValue
   end function
      The naming convention I adopted in this example is standard; get_ and put_ prefix the public name of the property. If this is what you want, you don't need to specify the internal name. The following code would suffice:

 <property name="ViewMode">
    <get />
    <put />
 </property>
If you want to follow a custom naming convention, you can use the internalname attribute.
      A method can have as many parameters as you need and you only have to list them using separate <parameter> tags. The name attribute refers to the public name of the method, which is the name a client must use to invoke it. However, internally you can associate that name with any routine you want. The optional internalname attribute specifies the name of the routine that will execute each time someone invokes the public name of the method. If you omit the internal name, then the public name is also considered the name of the component's routine providing that behavior.
      An event is characterized by a name and one or more arguments. To fire an event from within the WSC code, you use the fireEvent function, specifying the event name and its parameters.
      The <script> tag simply gathers all the code used within the component. You may have several blocks that use different scripting languages. Remember, to declare your WSC code to be compliant with the XML 1.0 specification, you must wrap the code of any script block in a CDATA section, as shown in this snippet.

 <?xml version="1.0"?>
 <component>
 •••
 <script language="VBScript">
 <![CDATA[
 •••
 ]]>
 </script>
 </component>
This saves you from mysterious and apparently odd errors if you're using special XML characters such as < and & in your script code. The <script> tag also supports the src attribute, which lets you import external code into your component.

 <script language="VBScript" src="procs.vbs" />
      Remember that since you're working in a script environment, all the variables and arguments involved are Variants; there's no way to specify data types.

Other Tags

      Additional WSC components can be defined in the same WSC file. Each is wrapped in a <component> tag, but all the <component> tags must be enclosed in a <package> section. There are a few tags that apply only on a per-component basis, including <object>, <reference>, and <resource>. The <object> tag lets you share the same instance of a given object among various <script> blocks. For example, if you have distinct <script> sections and all of them need to access the ActiveX Data Objects (ADO) object model, you can create a global instance of the object and make it accessible through its ID.


 <object id="ado" progID="ADODB.Recordset" />
      Note that you can use either the progID or the CLSID to identify the object. Creating an object instance doesn't reference the type library. You can do this by setting an optional reference attribute to true.

 <object id="ado" 
     progID="ADODB.Recordset" 
     reference="true" />
      Unfortunately, this won't work for all possible objects. In fact, if the object doesn't have the type library registered under its CLSID, there's no way to identify and load it. To work around this, you need to explicitly reference the type library using the <reference> tag.

 <reference progID="ADODB.Recordset" />
      The progID attribute can point to the type library progID or the progID of the same COM module if the module incorporates its typelib. Otherwise, you can directly specify the GUID of the library using the alternative GUID attribute.
      The <resource> tag is a way to insulate all the strings and numbers that you consider constants. You define them within a <resource> section and identify them through a unique ID, as shown here:

 <resource id="errCantCreateConn"> 
 Unable to create the connection
 </resource> 
 <resource id="errWrongArgs"> 
 Wrong number or types of arguments
 </resource>
      Unfortunately, you don't have a unique container for all these resources. Instead, you need to add a specific <resource> tag for any resource you want. To use such resources in the code, call the getResource function and pass the ID of the resource.

 <% 
 Response.Write getResource("errCantCreateConn") 
 %>
A Type Library for Windows Script Components

      WSC components are regular COM components and therefore it makes perfect sense to give them a type library. Once you've created a WSC file, right-clicking it from within Windows Explorer displays a context menu that has an option to generate a type library (see Figure 4).

Figure 4: Generating a Standard Type Library for a WSC
      Figure 4: Generating a Standard Type Library for a WSC

However, while it's quite handy, this option doesn't provide everything you really need; all of the settings—from the name of the typelib file to the various descriptions—will default to generic strings. Figure 5 demonstrates a Windows Script Host (WSH) script that creates a more descriptive type library. You can replace the standard procedure with this one by changing a registry entry. Under the key


 HKEY_CLASSES_ROOT
   \scriptletfile
     \shell
       \Generate Typelib
         \command
set the Default value to:

 wscript "c:\windows\typelib.vbs" "%L"
      This code asks the WSH runtime to execute the typelib.vbs script (see Figure 5), passing the selected file name in its long file name format. (Unless you specify the %L flag, the script passes the file name in its short 8.3 form. It's just a matter of readability; the script works in both cases.) The quotes help the script manage any blanks in the path name. Of course, feel free to copy the typelib.vbs to any path you want, but remember to change the command line accordingly. If you haven't yet installed WSH—part of Windows 98 and Windows 2000—you can get it from the Microsoft scripting site.

What You Need for WSC

      WSC is planned to be a standard component of Windows 2000 and is available as part of the Internet Explorer 5.0 download. To get everything you need to run WSC components, go to the Microsoft scripting site and download the WSC package. It installs all the runtime machinery and a very handy tool called Windows Script Component Wizard. This is all you need to start writing components.

Figure 6: Specifying Registration Information
      Figure 6: Specifying Registration Information

Figure 7: Defining Supported Interfaces
      Figure 7: Defining Supported Interfaces

       Figure 6 and 7 show the first two steps of the wizard, which let you specify registration information and define the supported interfaces (ASP code and behaviors) as well as the standard Automation interface. Figure 8 shows the source code of a sample COM component created by the WSC wizard. This sample is a simple component that validates the text entered into a textbox. It uses the Regular Expression object from VBScript 5.0 to do the job. Figure 9 shows an ASP page that utilizes the sample component.
Figure 9: Running the Sample Component
      Figure 9: Running the Sample Component

      Admittedly, this example doesn't show the true power of WSC. In fact, the validation implemented here is nothing but a wrapper for the methods of the RegExp object. However, in a real-world scenario you might need to integrate the validation procedure with database access to check a user's password. Notice, however, that at this point you have a script component that acts like a real COM object and manipulates the ASP object model directly.

The <implements> Tag

      The key instruction to make the ASP object model accessible from within the WSC script code is:


 <implements type="ASP" id="ASP" />
This code tells the WSC runtime engine (scrobj.dll) to load a COM object that acts as a proxy between WSC and the ASP object model. As shown in Figure 10, when a user requests an ASP page the Web server gives all objects on that page a chance to sink the OnStartPage event. (This doesn't apply to any objects with application scope that are defined in the global.asa file, since they already exist at this point.) The call to the object's OnStartPage event occurs the first time an ASP script accesses the object. In the WSC/ASP scenario, the ASP interface handler is a COM object that simply imports the ASP type library and obtains valid references to all the ASP built-in objects, like Response and Request. New objects are added to the global namespace in which the WSC script blocks execute. In this way, the WSC script blocks can successfully manage ASP objects as if they were running inside the ASP page that called the WSC.
Figure 10: Requesting an ASP Page
      Figure 10: Requesting an ASP Page

      In the syntax of the <implements> tag, notice the presence of an ID attribute. It's optional and, as far as I know, it's ignored for COM components. The type attribute, on the other hand, is a sort of keyword. While the WSC runtime is parsing the code for a given WSC file, this attribute helps it identify the object it needs to load to provide the additional required functionality. Currently, only ASP and behaviors are supported.


 <implements type="Behavior" />
The interface handler implements COM interfaces that are specific to the Microsoft® Internet Explorer 5.0 behaviors.
      Remember that each <component> tag in a WSC file defines a script component. When you have multiple components, you can uniquely identify them using the ID attribute. If you want to call a scriptlet from within another component, use the createScriptlet function:

 set wsc = createScriptlet("one")
      Figure 11 shows a WSC with two different scriptlets that call each other. From within an ASP or WSH script they are still two different components, as you can see from the following JScript® code:

 var obj1, obj2;
 obj1 = new ActiveXObject("One.WSC");
 obj1.Tell();

 obj2 = new ActiveXObject("Two.WSC");
 obj2.Speak();
      However, having a single scriptlet that supports both ASP and behaviors doesn't make much sense since their functionalities are so different.

WSC as a Script-based DTC

      WSC files have a number of limitations. Performance is one problem, but they also lack advanced language features and have restricted access to the Win32 API. For this reason, most WSC files are useful for generating repetitive and boring pieces of script code. A DTC was a kind of sophisticated macro used by the authoring tool, such as Visual InterDev; a WSC is a COM object that is instantiated when the ASP page is processed and can participate in the page construction. By writing a WSC, you can model templates for your pages and build them whenever necessary by setting some properties.
       Figure 12 shows a script-based COM component that depicts a page with a header, footer, sidebar, and an area reserved for content. The body of an ASP page using this component would look like this:


 <body>
 <%
     set page = Server.CreateObject("ScriptDtc.WSC")
     page.Title = "This is the title"
     page.SubTitle = "This is the subtitle"
     page.Footer = "(c) 1999"
     page.Sidebar = "Menu"
     page.Content = "<br>Welcome to this Web site!"
     page.DrawPage
 %>
 </body>
You can see the completed page in Figure 13. The WSC encapsulates all the details of the HTML code and deals with that boring <TABLE> and <TR> stuff. You're using a WSC as a generator of HTML and can code your page in a more structured way.
Figure 13: Running the WSC Page
      Figure 13: Running the WSC Page

      It takes more work to create a component that is flexible enough to produce a large part of your pages, but you should now see the possibilities. WSCs are just what their name suggests: script components. You should be using them primarily to componentize your script code.

A Word on Behaviors

      Behaviors are a new technology introduced by Internet Explorer 5.0. While the recommended way of writing behaviors is through HTML Components (HTC), you can write behaviors through WSC as well. An HTC is really just a simplified version of a WSC. Rather than writing a COM component to validate the content of an input box, you can define a new behavior for the <INPUT> tag that takes a regular pattern into account. This improved <INPUT> tag will check whether it's compliant with the specified pattern. Of course, this approach requires Internet Explorer 5.0 as the client.
      Once you define a behavior like the one in Figure 14, an HTML page may include an <INPUT> tag like this


 <input type=text 
     class="Valid" 
     Tip="Enter two words separated by a blank" 
     TipControl="TipText" 
     Mask="\w+ \w+">
where the class Valid is defined this way

 <style>
 .Valid {behavior:url(textbox.wsc)}
 </style>
and Mask, Tip, and TipControl are all properties specific to the extended input box. Mask contains the regular expression pattern to be matched. Tip is help text for the HTML tag pointed to by TipControl. The behavior of the new <INPUT> tag makes it change the background color to pink when its value doesn't match the pattern. At the same time, the help text is displayed on the specified tag on the host page. The only requirement is that this tag supports the innerHTML property (for example, if it's a <SPAN>). Figure 15 shows the final page.
Figure 15: A Login Page Using Behaviors
      Figure 15: A Login Page Using Behaviors

The Big Picture

      Bear in mind that the WSC syntax has evolved quite rapidly in the past year to keep up with the evolution of Windows scripting. Some functions and tag names may have changed, so be sure to check the latest documentation on the Microsoft scripting site. I examined WSC mainly from the ASP point of view. However, you can also write reusable code the COM way using VBScript or JScript. You can take advantage of this in ASP applications as well as Internet Explorer 5.0-based modules or WSH procedures.
      Another advantage of WSC is that—thanks to the language-neutrality of Windows Script (also known as ActiveX Scripting)—you have a powerful way to make your Perl or Rexx script COM-ready, making them reusable in all these environments. To take advantage of this power, make sure you install the WSC package properly. The runtime files come with Internet Explorer 5.0, WSH 2.0, and Windows 2000. Those are the only binaries you need on your machine. The documentation and the wizard must be downloaded separately from http://msdn.microsoft.com/scripting.

From the December 1999 issue of Microsoft Internet Developer