Dino Esposito |
ASP and Windows Script Components |
Download the code (5KB)
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. |
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. 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 |
However, while it's quite handy, this option doesn't provide everything you really need; all of the settingsfrom the name of the typelib file to the various descriptionswill 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 WSHpart of Windows 98 and Windows 2000you 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 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 |
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 |
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. |
<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 |
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. |
<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 |
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. |
From the December 1999 issue of Microsoft Internet Developer