In this chapter, you will learn how to use Visual Basic 5 to write ActiveX components that you can use in server-side applications. You will look at creating business components and components to be used specifically in an Active Server Page environment. You will learn about the thread model supported by Visual Basic 5 components, and you will take an existing Visual Basic class and turn it into an ActiveX component. Creating OLEISAPI components is covered as well.
Visual Basic 5 enables you to easily generate ActiveX components (called OLE servers in Visual Basic 4). The ActiveX components you create can be used by other applications or development environments, such as Microsoft Excel or Powersoft's PowerBuilder. You can use ActiveX components in a client/server environment, desktop environment, distributed computing environment, or Internet/intranet environment. This chapter focuses on creating ActiveX components to be used in an Internet/intranet environment--more specifically, the Internet/intranet server environment. You can use ActiveX components in a Web-based client much in the way you use ActiveX controls. Instead of an ActiveX control loading to the user's PC, the ActiveX component loads to a user's PC and is used via VBScript. This chapter focuses on building ActiveX server components. You will look at the following types of server components:
Active Server Pages provide the perfect environment for using ActiveX server components. Most of your Web-based server applications probably will be written using ASP, so this chapter concentrates on using ActiveX components in an ASP environment. The standard, non-ASP environment will be covered as well, via OLEISAPI.
What this chapter refers to as standard ActiveX components are those components in your organization that can be used by any tool or application that supports ActiveX components. Standard components can be the business rules created by your company and used by other applications for validation and computations. The important point about a standard ActiveX component is that you do not have to modify it in any shape or fashion to use the component in any ActiveX-compliant tool or application, such as ASP, Visual Basic, or Microsoft Access.
ActiveX components designed for ASP are components that take advantage of Active Server Page intrinsic objects or components. You build the component to use objects provided in the ASP environment. Because the components rely on ASP objects, they will not work outside of the ASP environment. These components could be business rules, utility functions, or other operations that are difficult to perform with standard Web-based tools.
When the Microsoft Internet Information Server (IIS) first shipped, ISAPI was (and still is) the API set provided to C and C++ programmers to extend the capabilities of the Web server. Visual Basic programmers were left out of the loop until Microsoft developed the OLEISAPI DLL (OLE2ISAPI.DLL) that enables Visual Basic programmers to use ActiveX components in standard HTML. This chapter will briefly cover OLEISAPI; however, I would no longer use OLEISAPI now that Microsoft Active Server Pages and the ActiveX Server framework are available. The ActiveX Server framework is a much better environment for using ActiveX components than OLEISAPI. I'll go into more detail about why I think OLEISAPI is outdated when the topic is covered in detail later in this chapter in the section "Using OLEISAPI."
ActiveX server components are simple to generate using Visual Basic 5. You can create components that are out-of-process (*.exe) or in-process (DLLs). Using out-of-process versus in- process is discussed later in this chapter in the section "Selecting the Proper Component Type (DLL or EXE) and Thread Model."
This section walks you through an example of creating an ActiveX DLL that you can use on a Microsoft IIS Web server. For this example, you will use the class DAOHTML, which was developed in Chapter 8, "Using Classes and Objects in Visual Basic 5."
Start a new Visual Basic ActiveX DLL project. Copy the file DaoHTML.cls from the CD-ROM at the back of this book to a working directory on your PC. Perform the following steps:
FIGURE 22.1. The Add Class Module dialog box.
FIGURE 22.2. The Add Module dialog box.
Sub Main
FIGURE 22.3. The References dialog box.
FIGURE 22.4. The Project Properties dialog box.
FIGURE 22.5. The Properties dialog box.
FIGURE 22.6.The Make Project dialog box.
You now have created an in-process ActiveX component. You can use the ActiveX component you created just like you would any other ActiveX component. First, you add a component reference to your project using the References dialog box. Then, you create a new instance of the component using Visual Basic 5, as this code shows:
Dim oTest As New MyFirst.DAOHTMLTable
The following example creates a new instance of MyFirst in an Active Server Page:
<% set oTest = Server.CreateObject("MyFirst.DAOHTMLTable")%>
Notice the string name used to reference an ActiveX component in ASP. The first part of the string is the name of the Visual Basic project used to create the component. The second part of the string is the module class name exposing the properties and methods you wish to use.
Before you can use your ActiveX component, you must register the component on your Windows NT Web server. Registering a component writes information about your component to the server's system Registry. You can automatically register an out-of-process ActiveX component (*.exe) by running the component on the server. To register an in-process ActiveX component requires the utility regsrv32.exe. This utility is located on the Visual Basic Professional and Enterprise CD-ROMs. If you installed Active Server Pages on your Windows NT Server and used the standard defaults, you can find the regsrv32 utility in the directory <drive letter>:\winnt\system32\inetsrv\asp\cmpnts.
To register your ActiveX in-process component, perform these steps:
regsrv32 full path of the ActiveX component/component file name
A message box appears, telling you that your component has been registered successfully. To register the ActiveX component created in the preceding example (assuming that the component is stored in a directory called c:\components), you would enter the following at the command prompt:
regsrv32 c:\component\MyFirst.dll
Visual Basic 5 enables you to create ActiveX code components that are in-process (DLLs) or out-of-process (EXEs). Further complicating the type of component to create is the thread model to select. Visual Basic 5 enables you to mark components as thread safe and to generate components that support multiple threads or single threads. Because the focus of this chapter is on creating server-side ActiveX components that will be used in the Microsoft ActiveX Server framework, the type of component to generate is simplified, as well as the proper thread model. For ActiveX server-side components, should you create an in-process (DLL) or out-of-process (EXE) component? For the answer, read the following tip.
Before examining the thread model used by Visual Basic 5 when creating ActiveX components, take a quick look at the definition of a thread.
A thread is executing code. Every application in a Windows environment has at least a single thread of execution. An application or component is multithreaded if the application can create more than one thread of execution. Suppose that you have a financial database application and you have a computation that executes for a long time. If your application is multithreaded, you can start your computation by creating a thread to perform the computation. Then, while the computation thread is executing, you can begin to edit a database table using another thread. Multitasking preemptive operating systems, such as Windows NT and Windows 95, allocate separate time slices for each thread to execute (the computation thread and the edit table thread, for example), which gives the appearance of performing both tasks simultaneously. A thread model describes the environment in which a single or multiple thread executes and interacts with other threads. The Active Server framework supports several thread models. As a Visual Basic developer, however, you will be concerned only with the thread model used by all Visual Basic components (in-process and out-of-process), which is the Apartment Thread model shown in Figure 22.7.
In the Apartment Thread model, each thread contains its own container, or apartment,
where all the objects created and used by the thread reside. The objects created
in the apartment are unaware of other objects residing in other apartment threads.
Each apartment has its own copy of global variables, which eliminates the possibility
of multiple threads overwriting global data variables and makes the Apartment Thread
model safe for use in multithreaded clients. Objects in the same apartment can share
information via global variables without any performance penalty. Objects residing
in other apartments can share information with each other by using object references.
When objects communicate across apartments, a mechanism called cross-threaded marshaling
is used. The performance of cross-threaded marshaling is slow and is comparable to
the performance of cross-process marshaling used to communicate with out-of-process
ActiveX components.
FIGURE
22.7. The Apartment Thread model.
Every class you create with Visual Basic has an Instancing property,
which determines how the class or object is created (see Figure 22.8). When developing
ActiveX server components, you should create your ActiveX server components as DLLs
(in-process components) most of the time to simplify the values of the Class
Instancing property.
FIGURE
22.8 The Class Instancing property.
The default value of the Instancing property for an ActiveX DLL is MultiUse
(an integer value of 5) and single threaded. For an ActiveX server-side component,
always set the Instancing property to MultiUse. When a DLL is created
using the MultiUse property and a single thread, a single copy of the object
is loaded into the address space of the client. What happens when multiple users
want to use the same object? To understand the behavior of a MultiUse component
with multiple users, take a look at the example in Figure 22.9.
FIGURE
22.9. A MultiUse, single-threaded
ActiveX component with multiple users.
The 401K object is used to manage an employee's 401(k) investment plan and has two methods: ComputeValue and ShowInvestments. The ComputeValue method takes several minutes to perform, and the ShowInvestments method occurs simultaneously. User A accesses a Web page that creates an instance of the 401K object, which is an ActiveX single-threaded DLL created with Visual Basic 5. User A begins to execute the ComputeValue method. User B accesses the same Web page and shares the same DLL for the 401K object being used by User A. User B executes the ShowInvestments method; however, the results are not returned immediately. Instead, User B finds herself blocked waiting for User A's ComputeValue method to complete execution. So what's going on? When the Instancing property of a component is set to MultiUse, a single instance of the DLL can be shared by multiple users simultaneously to create objects. What if users overwrite each other's data? No problem: To prevent user requests from overlapping and possibly overwriting global or local variables, the ActiveX component uses serialization. Serialization ensures that the component will execute only one user request at a time from start to completion. Pending requests are queued up and executed in turn. Using the example in Figure 22.9, User B is blocked waiting for User A's request to complete. Blocking quickly can become a big problem in an Internet/intranet environment. Fortunately, Visual Basic 5 enables you to create multithreaded ActiveX DLLs.
With Visual Basic 5, you can create multithreaded ActiveX DLLs. Use the example
with the 401K object but, this time, assume that the DLL is created to be
multithreaded. Examine Figure 22.10 to walk through the multiuser example and see
how you can avoid blocking.
FIGURE
22.10. A MultiUse, multithreaded
ActiveX component with multiple users.
User A accesses a Web page that creates an instance of the 401K object, which is an ActiveX multithreaded DLL created with Visual Basic 5. User A begins to execute the ComputeValue method. User B accesses the same Web page and shares the same DLL for the 401K object being used by User A. User B executes the ShowInvestments method, and the results are returned immediately. User B is not blocked waiting for User A's request to complete. Instead, in a multithreaded ActiveX component, when User B creates a 401K object, User B's 401K object creates a separate thread and apartment in which User B's objects can reside. Any object that User B creates will live inside User B's apartment thread, as shown in Figure 22.10. Objects in User B's apartment can communicate with each other, because they are in the same address space. Objects also can communicate with objects in other threads using cross-process marshaling. The cross-process call is serialized in the thread you are calling, and the calling thread is blocked until the request can be completed.
When you create a multithreaded ActiveX server DLL, the component cannot require any user interaction. ActiveX DLLs that use any of the following cannot be multithreaded:
Message boxes and system-error messages are suppressed and can be written to the Windows NT Event log by setting the Visual Basic App object's LogMode property. To set the LogMode property, use the App object's StartLogging method. ActiveX DLLs that require user interaction or that contain controls, forms, ActiveX documents, or classes generated by ActiveX designers are only single threaded.
To create a multithreaded ActiveX DLL, perform these steps:
FIGURE 22.11. The Project Properties dialog box.
When the ActiveX DLL is compiled with the Unattended Execution checkbox enabled, a DLL that supports multithreading is generated. When creating multithreaded ActiveX components, keep in mind that Visual Basic DLLs use Apartment model threading and that ActiveX component automation uses serialization of requests to prevent multiple threads from executing a new operation before previous operations have completed. Keeping component serialization intact is important, because Visual Basic ActiveX components are not re-entrant. Re-entrancy is the capability of code to be executed by a thread, and, before the thread completes, the thread yields control of the processor to another thread to process the same code. When the second thread yields processor control, the variables and stack pointer are restored to the exact state prior to the processor yielding control to another thread. Visual Basic ActiveX components are not re-entrant, so when creating ActiveX components, do not do any of the following in your component, because it might cause the processor to yield to another thread before completing the current operation:
Now that you have learned that you should create ActiveX components that are multithreaded DLLs, start to think of all the different server-side ActiveX components you can use to energize your Web pages and how your Visual Basic skills can continue to aid you in the Web development environment.
When Microsoft released IIS, the ISAPI API was released to enable C programmers to write ISAPI programs and filters to extend the services provided by IIS. Well, what about the Visual Basic programmers? OLEISAPI was Microsoft's answer. OLEISAPI is a DLL provided by Microsoft that enables Visual Basic developers to use ActiveX components in Web pages with IIS. OLEISAPI was okay when it was the only way for a Visual Basic programmer to use ActiveX components with Microsoft's IIS. But with the release of IIS 3.0 and the ActiveX Server framework, I strongly urge you not to use OLEISAPI unless you are using an older version of Microsoft IIS that does not support Active Server Pages.
Here are a few reasons why using ActiveX components with Active Server Pages is preferred over OLEISAPI:
The bottom line is that, if you currently are using OLEISAPI and you can switch to Active Server Pages, do so! If you do not have Active Server Pages installed on your IIS, what are you waiting for? Time to upgrade!
If you still want to experiment with OLEISAPI, Microsoft includes OLEISAPI examples on the VB 5 CD as well as on its Website. You also must make sure that oleisapi.dll is in a directory with execute permission. When creating an OLEISAPI component, you must add the method you will use to invoke the component. If you want to submit a form for a guest registration database using the HTML form action Post, for example, you would add the following method to your class:
Sub DoGet(strHTMLrequest As String, strHTMLResponse As String) `Add code to parse the HTML string sent from the Server `Add code to determine the proper Method you wish to invoke for your component `For instance, if you were adding an entry to a Guest Registration Database Âyou `could call a function or procedure called AddGuest `Add code to properly format an HTML response and then set the string `strHTMLResponse to the HTML string you want to send back to the browser. End Sub
The following HTML shows how to call a component from a Web page using OLEISAPI:
<FORM ACTION="/oleisapi/oleisapi.dll/GuestAdd.WebOLE.DoPost" METHOD="Post">
In the HTML line using OLEISAPI, GuestAdd is the component, WebOLE is the Visual Basic class and DoPost is the method. One last comment on OLEISAPI: In the next two sections, you will see how much simpler it is to use existing and new ActiveX components in your Web pages, because they require fewer restrictions and programming than OLEISAPI.
By using Active Server Pages, you can use existing ActiveX components in your Web pages that also can be used in any application or development tool that supports ActiveX components, such as Microsoft Excel, Powersoft's PowerBuilder, Borland's Delphi, or Microsoft's Visual Basic. Unlike OLEISAPI, ASP enables you to use existing ActiveX components in your Web pages without any component modifications required.
What kind of coding routines make good ActiveX components? The typical scenario given for generating standard ActiveX components is the three-tier architecture for software systems. The typical three-tier system consists of the upper tier, called user services, which provides user interaction services; a middle tier, called business services; and the bottom tier, which provides data services. In a three-tier architecture, ActiveX components are the perfect solution for the middle-tier business rules. ActiveX component reuse enables you to create the business rule one time and to use the rule-based component in other applications, including Web-based applications.
For this book, instead of creating a fictitious ActiveX business-rule component to use in an Active Server Page, you'll generate a component that enables you to use one of my favorite missing functions from VBScript in a Web page: the Visual Basic Format function. The Format function enables you to take a numeric or date expression and format the expression based on a specific format mask you provide. VBScript does not support the Format function, so to display a date in the format Tuesday, January 28, 1997 requires some work using VBScript, not to mention that changing the date format to 19970128 requires more VBScript work. The syntax for the Format function follows:
Format(express [, format[, firstdayofweek[, firstweekofyear]]])
where express is the numeric or date expression to evaluate, and format is the mask to use to properly format the expression. firstdayofweek and firstweekofyear are Visual Basic constants and optional parameters that will not be used in your ActiveX component. The ActiveX component will be a simple component that wraps a few lines of code around the Format function and exposes methods and properties that permit you to use the Format function in an Active Server Page. The ActiveX component is called FormatVB.DLL. The component will be generated as an in-process (DLL), multithreaded ActiveX component.
The FormatVB component is based on the NumDate class, which has the following properties:
The Expression property is a variant data type and holds the number or date to format. The FormatMask property is a string that holds the mask to use to format the expression. The ErrorMessage property helps you debug the component. If an error occurs while formatting an expression, the ErrorMessage property contains the Visual Basic error message generated; otherwise, for successful operations, it is empty. The property FType determines whether the expression is a date or numeric value. FType is not really required for this component, because the Format function takes a variant data parameter for the expression. I included it, however, to show you how to use Visual Basic 5 enumerations. Enumerations enable you to define groups of constants. Even more important, when properly defined, enumerations show up in the Object Browser for your ActiveX component as well as in the Auto-Code feature of the Visual Basic Editor when setting your components properties that have enumerations.
The following enumeration, for example, declares two constants that will be assigned the values 0 and 1:
`Valid Property Values for the Type property Public Enum FTypeValue ftDate ftNumeric End Enum
Now, to tie the enumeration to a property to take advantage of the Auto-Code feature of the Visual Basic Editor and Object Browser, assign the enumeration type as the return or input data type of the property. For example, the FType Get property procedure follows:
Public Property Get FType() As FTypeValue `used when retrieving value of a property, on the right side of an `assignment. Syntax: Debug.Print X.Type FType = mvarType End Property
The FormatVB object contains a single method called DoFormat that returns a variant with the properly formatted date or numeric. The DoFormat method checks to see whether the Expression property contains a valid numeric or date. If the expression is a valid numeric or date, the DoFormat property executes the Visual Basic Format function and returns the formatted output. Listing 22.1 shows the entire code for the NumDate class.
Listing 22.1. The NumDate Class.
`local variable(s) to hold property value(s) Private mvarExpression As Variant `local copy Private mvarFormatMask As String `local copy Private mvarErrorMessage As String `local copy Private mvarType As Integer `local copy ` `Valid Property Values for the Type property Public Enum FTypeValue ftDate ftNumeric End Enum Public Property Let FType(ByVal vData As FTypeValue) `used when assigning a value to the property, on the left side of an `assignment. Syntax: X.Type = 5 mvarType = vData End Property Public Property Get FType() As FTypeValue `used when retrieving value of a property, on the right side of an `assignment. Syntax: Debug.Print X.Type FType = mvarType End Property Public Property Let ErrorMessage(ByVal vData As String) `used when assigning a value to the property, on the left side of an `assignment. Syntax: X.ErrorMessage = 5 mvarErrorMessage = vData End Property Public Property Get ErrorMessage() As String `used when retrieving value of a property, on the right side of an `assignment. Syntax: Debug.Print X.ErrorMessage ErrorMessage = mvarErrorMessage End Property Public Function DoFormat() As Variant Dim vReturnValue As Variant `Set up an Error Handler On Error GoTo DoFormat_Error `Initialize the return values vReturnValue = "" mvarErrorMessage = "" `Check to make sure the expression to format is `valid for the type selected If mvarType = ftDate Then If Not IsDate(mvarExpression) Then mvarErrorMessage = "The value in the Expression " & _ "property is not a valid date." End If Else `Check for valid Numeric Value If Not IsNumeric(mvarExpression) Then mvarErrorMessage = "The value in the Expression " & _ "property is not a valid numeric." End If End If `If the expression is a valid date or numeric `perform the Format function. If mvarErrorMessage = "" Then vReturnValue = Format(mvarExpression, mvarFormatMask) End If `Standard Exit DoFormat_Exit: `Return the Formatted value DoFormat = vReturnValue Exit Function `Standard Error Handler ` DoFormat_Error: mvarErrorMessage = "Error Formatting expression. " & Err.Description vReturnValue = "" Resume DoFormat_Exit End Function Public Property Let FormatMask(ByVal vData As String) `used when assigning a value to the property, on the left side of an `assignment. Syntax: X.FormatMask = 5 mvarFormatMask = vData End Property Public Property Get FormatMask() As String `used when retrieving value of a property, on the right side of an `assignment. Syntax: Debug.Print X.FormatMask FormatMask = mvarFormatMask End Property Public Property Let Expression(ByVal vData As Variant) `used when assigning a value to the property, on the left side of an `assignment. Syntax: X.Expression = 5 mvarExpression = vData End Property Public Property Set Expression(ByVal vData As Object) `used when assigning an Object to the property, on the left side of `a Set statement. Syntax: Set x.Expression = Form1 Set mvarExpression = vData End Property Public Property Get Expression() As Variant `used when retrieving value of a property, on the right side of an `assignment. Syntax: Debug.Print X.Expression If IsObject(mvarExpression) Then Set Expression = mvarExpression Else Expression = mvarExpression End If End Property
The ActiveX component is complete; you're ready to test the component (remember that, with Visual Basic 5, you do not need a dummy empty procedure for an entry point when generating ActiveX components). The easiest way to test a standard ActiveX component is to use the same method used in Visual Basic 4 to test OLE Automation servers. From the Visual Basic design environment, run the ActiveX component project. Start a second Visual Basic session and add the ActiveX component currently running in the first Visual Basic session to the References dialog box of the new project. Create a test program that creates an object using your ActiveX component. Add code in the test program to set the methods and properties of your object. Then run and execute the test program. You can use the debugger in both Visual Basic sessions to debug your component or test program.
After the ActiveX component is tested properly, compile the ActiveX component to build the DLL. Don't forget to set the proper project options to make the DLL multithreaded. After you compile the ActiveX component, you need to register the DLL on your Web server using the regsrv32 program discussed earlier. You now are ready to use the ActiveX component on a Web page. Listing 22.2 shows the Active Server Page created to test the FormatVB component.
Listing 22.2. Source code to the Active Server Page to test the ActiveX component FormatVB.
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <!-- Active Server Page Example Using a Visual Basic ActiveX Component --> <!-- Author: Mark Spenik --> <!-- Revision History: Jan. 31, 1997 --> <!-- This example uses an ActiveX component to provide --> <!-- the Visual Basic Format function in a Web Page. --> <!-- --> <SCRIPT LANGUAGE=VBScript RUNAT=Server> Function FormatExpression `Initialize the default string sExpress = "" `Create an instance of the ActiveX Component Set oFormat = Server.CreateObject("FormatVB.NumDate") `Check if it's a date - if so set the type property If IsDate(Request.Form("txtExpression")) then `Set the value to reflect a date oFormat.Ftype = 0 Else `Set the required properties oFormat.FType = 1 End If `Set the Expression property from the submitted form oFormat.Expression = Request.Form("txtExpression") `Set the FormatMask property from the submitted form oFormat.FormatMask = Request.Form("txtFormatMask") `Format the String sExpress = oFormat.DoFormat `Check For Errors If oFormat.ErrorMessage <> "" Then `Error found display back in the Web Page sExpress = oFormat.ErrorMessage End If `Cleanup - A good VB Practice - although with ASP not required. Set oFormat = Nothing `Return the string to the caller FormatExpression = sExpress End Function </SCRIPT> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <meta name="GENERATOR" content="Microsoft FrontPage 2.0"> <title>Home Page</title> </head> <body bgcolor="#000080" text="#FFFF00"> <p><font color="#FF0000"><marquee bgcolor="#FFFFFF">Testing a Standard ActiveX Component</marquee></font></p> <hr color="#FF0000"> <!-- In this example, we are posting back to the same ASP file that --> <!-- submits the request. As such first parse the form to see if it has --> <!-- Been entered properly. --> <% If Request.Form("txtExpression")="" Then sMsg = "Enter an expression to format." ElseIf Request.Form("txtFormatMask")="" Then sMsg = "Enter a valid format mask." Else sMsg = FormatExpression End If %> Enter a date or numeric and a format mask. For example: <BR> a date of Jan. 1, 1997 and a Format Mask of mmddyyyy will <BR> return 01011997. <form method="POST" name="frmExpression" Action="format.asp"> <p>Expression <input type="text" size="22" name="txtExpression" VALUE="<%=Request.Form("txtExpression")%>"></p> <p>Format Mask <input type="text" size="22" name="txtFormatMask" VALUE="<%=Request.Form("txtFormatMask")%>"></p> <p><input type="submit" name="cmdFormat" value="Format"></p> </form> <hr color="#FF0000"> <!-- Send back the formatted Expression or instructions. --> <B><%=sMsg%></B> </body> </html>
The Active Server Page shown in Listing 22.2 is a bit complex because the form posts the information back to the originating ASP file (Format.asp). To post information back to the originating ASP file, set the Action tag of the form to the originating ASP filename. In Listing 22.2, for example, the following line posts the form back to Format.asp, the originating file:
<form method="POST" name="frmExpression" Action="format.asp">
The following script examines the values of the Request object to determine whether the form has been submitted or is being loaded or refreshed:
<!-- In this example, we are posting back to the same ASP file that --> <!-- submits the request. As such first parse the form to see if it has --> <!-- been entered properly. --> <% If Request.Form("txtExpression")="" Then sMsg = "Enter an expression to format." ElseIf Request.Form("txtFormatMask")="" Then sMsg = "Enter a valid format mask." Else sMsg = FormatExpression End If %>
If the form has been submitted, all the Request objects will have valid values, and the function FormatExpression is called (see Listing 22.2 for a full listing of the FormatExpression script). The following script found in the function FormatExpression creates an instance of the FormatVB object:
`Create an instance of the ActiveX Component Set oFormat = Server.CreateObject("FormatVB.NumDate")
The FormatExpression function returns a formatted string or error message. The output of the FormatExpression is returned to the browser with the following line of code:
<!-- Send back the formatted Expression or instructions. --> <B><%=sMsg%></B>
Figure 22.12 shows the completed Web page.
FIGURE
22.12. An Active Server page to test the ActiveX component FormatVB.
You should remember some of these important points about using standard ActiveX components on a Web page:
In addition to generating standard ActiveX components that can be used in application or development environments, you can generate ActiveX components specifically designed to be used in the ASP environment. These components can interact with standard ASP objects, such as the Application or Response object. Building ActiveX components that interact with ASP objects requires registering the Active Server page DLL, asp.dll, on your local development machine and adding a project reference to the DLL. The DLL, as well as the Active Server Page environment, can be loaded by using Microsoft Visual InterDev or by copying asp.dll from your Microsoft IIS 3.0 (located in the directory \Intsrv\ASP) to your development machine. Although I slammed OLEISAPI because of the templates and modifications required to use a standard ActiveX component via OLEISAPI, I really like the capability to interact with the ASP environment directly via an ActiveX component written specifically for the ASP environment. I think you will find many instances when you will want to design components that you plan to use only in Web-based applications. Creating ASP ActiveX components enables you to write code and perform tasks in a familiar Visual Basic environment while interacting with ASP objects.
The only difference between creating a standard ActiveX component and an ASP ActiveX
component is that the ASP ActiveX component has a reference to the Microsoft Active
Server Pages 1.0 Object Library, selected in the Visual Basic project References
dialog box shown in Figure 22.13, and the component reference's ASP objects, such
as the Response object in code.
FIGURE
22.13. The Visual Basic project References dialog box with the
Microsoft Active Server Pages 1.0 Object Library selected.
The Active Server Page Object Library provides several object classes that can be used in Visual Basic ActiveX components. To use Active Server Page object classes in your Visual Basic ActiveX components, you must obtain a reference to the object you want to use. You can obtain an ASP object reference by passing an ASP object reference to your component (as a method parameter or by setting a property) or by using the ASP ScriptingContext class and the OnStartPage method (the OnEndPage method also is supported). Using the OnStartPage method is simpler, because a special object called the ScriptingContext object is passed automatically by IIS as a method parameter. The ScriptingContext object supplies methods that can be used to obtain references to the following Active Server Page objects:
By using the ScriptingContext object, your component can generate references to the desired ASP object without relying on the Web page author to correctly pass the required ASP references used by your component. Now look at a quick scenario on how the OnStartPage and OnEndPage methods are handled. When a Web page is retrieved that uses ActiveX components, the server looks for OnStartPage methods for all components on the page except those that have application scope. The OnStartPage method is executed for all the components before any script executing (remember that IIS passes a ScriptingContext object reference as a parameter in the OnStartPage method). When all the scripts on the page complete, the OnEndPage method is invoked for ActiveX components that have defined the method. The OnStartPage and OnEndPage methods are never called for application-scope ActiveX components. The only way to create application-scope objects from ActiveX components created with Visual Basic is to use the <OBJECT> tag in the Global.asa file. Any required ASP references must be passed into the component's methods or properties.
In this section, you'll enhance the timesheet application created in Chapter 21, "Active Server Pages, OLE DB, and Active Data Objects." You'll add an ActiveX component that displays previous timesheets added in an HTML table. You'll also modify the application to post the timesheet to the same ASP file instead of using multiple ASP files. The ASP ActiveX component will have a single method called GetEmpTime that will retrieve all the previous records entered by an individual and display them at the bottom of the HTML page. The Visual Basic ActiveX component will use the method OnStartPage to obtain a reference to the ASP Scripting Context object. The Visual Basic component will create an instance of the ASP Request object to read the employee name from the form to use for the timesheet retrieval query. An instance of the Response object will be created to send back the HTML table of timesheets entered to the client browser. ADO will be used to retrieve information from a Microsoft Access database. The ActiveX component also will log informational and error events to the Windows NT application Event log by using the Visual Basic App object.
Figure 22.14 shows the completed Web page application with retrieved records.
FIGURE
22.14. The timesheet application using
an ActiveX component.
The Visual Basic 5 source code for the component is a single class called GetTime. The class has no properties and a single method called GetEmpTime. The ActiveX component generated is called ASPComp.dll. Listing 22.3 shows the code for the GetTime class.
Listing 22.3. Source code for the GetTime class that makes up the ActiveX component ASPComp.
`Define a private Variable to use to generate any `ASP object required for our component Private goCurrentScript As ASPTypeLibrary.ScriptingContext Public Function OnStartPage(oScriptContext As ASPTypeLibrary.ScriptingContext) Dim strLogFile As String `This function is called when the Web page that uses `this component is being loaded. IIS will pass in the `references to the ASP ScriptingContext object. `Set a global reference to the Scripting Context object `to use in the Component's `methods. Set goCurrentScript = oScriptContext `Let's start logging the component's progress `using the Visual Basic App object. `On Windows NT the Log file is written to the Application `event log - on Windows 95 - to the File specified. strLogFile = App.Path & "\" & "axcomp.log" App.StartLogging strLogFile, vbLogAuto `Log the Startup Event App.LogEvent "OnStartPage Method Invoked for ASPComp", ÂvbLogEventTypeInformation End Function Public Function OnEndPage() `Log the terminate event App.LogEvent "OnEndPage Method Invoked for ASPComp", vbLogEventTypeInformation `Clean up global object reference Set goCurrentScript = Nothing End Function Public Function GetEmpTime() Dim oRequest As ASPTypeLibrary.Request `Request Object Dim oResponse As ASPTypeLibrary.Response `Response Object Dim rsEmployee As New ADODB.Recordset Dim strErrMsg As String, strSQL As String Dim strHTMLReturn As String Dim intNumOfColumns As Integer Dim fldRec As Field Dim colFields As Fields Dim intCount As Integer `Generic Counter `Set up a generic Error Handler On Error GoTo GetEmpTime_Error strErrMsg = "" `Check for the global script context object If (goCurrentScript Is Nothing) Then strErrMsg = "Global reference to ScriptingContext object missing." & _ " Make sure the object does not have Application scope and " _ & " that you use Server.CreateObject." GoTo GetEmpTime_Exit End If `Obtain the required ASP references Set oRequest = goCurrentScript.Request Set oResponse = goCurrentScript.Response `Set up the SQL string required to retrieve all `timesheets for the current employee (i.e one submitted the form) strSQL = "Select Employee, WorkDate, ClientName, Billcode, Hours From EmpTime ÂWhere Employee = `" strSQL = strSQL & oRequest.Form("txtEmployee") & "`" `Open a resultset based on the SQL Statement rsEmployee.Open strSQL, "DSN=TimeSheet" `App.LogEvent "All Objects Created.", vbLogEventTypeInformation `If No records in the recordset - exit now If (rsEmployee.BOF) And (rsEmployee.EOF) _ Then GoTo GetEmpTime_Exit `Begin the Table format using the TAG Table strHTMLReturn = "<TABLE width=100% cellspacing=0 cellpadding=0 " `Add the table Background color strHTMLReturn = strHTMLReturn & "BGCOLOR = white ALIGN=Center>" & vbCrLf `Add the Table Caption strHTMLReturn = strHTMLReturn & "<CAPTION><B>" & _ "Previous timesheets for employee: " & _ oRequest.Form("txtEmployee") & "</B></CAPTION><P>" & vbCrLf `Basic HTML string is set up - get the Number of Columns intNumOfColumns = rsEmployee.Fields.Count - 1 `Make 0 based Set colFields = rsEmployee.Fields `Add column Headers For Each fldRec In colFields strHTMLReturn = strHTMLReturn & "<TH>" & fldRec.Name Next fldRec strHTMLReturn = strHTMLReturn & vbCrLf `Add The data rows ` Do until all the records have been processed While Not rsEmployee.BOF And Not rsEmployee.EOF strHTMLReturn = strHTMLReturn & "<TR>" For intCount = 0 To intNumOfColumns `Add proper HTML Tags for each column strHTMLReturn = strHTMLReturn & "<TD>" & _ rsEmployee(intCount) & "</TD>" & vbCrLf Next intCount strHTMLReturn = strHTMLReturn & "</TR>" `Get the Next Record rsEmployee.MoveNext Wend rsEmployee.Close `End the Table TAG strHTMLReturn = strHTMLReturn & "</TABLE>" App.LogEvent "Completed building table", vbLogEventTypeInformation `Send the table to the Browser oResponse.Write strHTMLReturn `Single Exit Point GetEmpTime_Exit: `Clean up any component references that were obtained If Not (oRequest Is Nothing) Then Set oRequest = Nothing End If If Not (oResponse Is Nothing) Then Set oResponse = Nothing End If If Not (rsEmployee Is Nothing) Then Set rsEmployee = Nothing End If GetEmpTime = strErrMsg Exit Function `Generic Error Handler GetEmpTime_Error: `Get the Error Message strErrMsg = "Error retrieving employee time. " & Err.Description `Write to the application log App.LogEvent strErrMsg, vbLogEventTypeError Resume GetEmpTime_Exit End Function
Now take a closer look at some of the code. The OnStartPage method is called when Active Server Pages is loaded. An instance of the ASP ScriptingContext object is passed into the method by IIS. A global private copy of the ScriptingContext object is set for later use, as shown here:
Set goCurrentScript = oScriptContext
The App object is used to set up message logging to the Windows NT Event log, as well as to log a message to the Event log using this code:
`Let's start logging the component's progress `using the Visual Basic App object. `On Windows NT the Log file is written to the Application `event log - on Windows 95 - to the File specified. strLogFile = App.Path & "\" & "axcomp.log" App.StartLogging strLogFile, vbLogAuto `Log the Startup Event App.LogEvent "OnStartPage Method Invoked for ASPComp", ÂvbLogEventTypeInformation
To read the submitted form values in the ActiveX component, instead of passing values through properties or methods, an instance of the ASP Request object is created to read the submitted HTML form. An instance of the ASP Response object is created to send information back to the browser. The following code uses the ScriptingContext object to create the ASP objects:
`Obtain the required ASP references Set oRequest = goCurrentScript.Request Set oResponse = goCurrentScript.Response
The code required to retrieve the HTML form value txtEmployee to set up the ADO query to retrieve previously entered timesheets from the database looks very similar to VBScript code used in ASP. The code follows:
`Set up the SQL string required to retrieve all `timesheets for the current employee (i.e one submitted the form) strSQL = "Select Employee, WorkDate, ClientName, Billcode, Hours From EmpTime Where Employee = `" strSQL = strSQL & oRequest.Form("txtEmployee") & "`"
The code to send the formatted HTML table back to the browser also looks very familiar (ASP):
`Send the table to the Browser oResponse.Write strHTMLReturn
Listing 22.4 shows the Active Server Page that uses the ASPComp.
Listing 22.4. Active Server Page script SrvTimes.ASP.
<HTML> <!-- Active Server Page Example Using Active Data Objects --> <!-- Author: Mark Spenik --> <!-- Revision History: Jan. 6, 1997 --> <!-- This example uses Active Data Objects to fill a combo box --> <!-- and to store the data entered in the timesheet. --> <!-- An ActiveX component is used to display previous time entered. --> <!-- This example posts the form back to this file and performs --> <!-- all validation code on the Server. This ASP can be used in any --> <!-- Browser. --> <!-- --> <SCRIPT LANGUAGE=VBScript RUNAT=Server> Function AddTime `Create an instance of the ADO object Set oDBTime = Server.CreateObject("ADODB.Connection") `Open the database oDBTime.Open "TimeSheet" `Create an ADO recordset to add a new record set rsTimeSheet = CreateObject("ADODB.Recordset") `Set the Recordset Properties rsTimeSheet.CursorType = 0 rsTimeSheet.LockType = adLockOptimistic rsTimeSheet.ActiveConnection = oDBTime rsTimeSheet.Source = "Select * From EmpTime" rsTimeSheet.Open `Check to make sure Updates are supported sResponse = "<H2>Sorry your timesheet was not added. Invalid server cursor. Â</H2>" If rsTimeSheet.Supports(adUpdate) Then `Add a new record rsTimeSheet.AddNew `Set the data values rsTimeSheet("Employee") = Request.Form("txtEmployee") rsTimeSheet("WorkDate") = Request.Form("txtWorkDate") rsTimeSheet("ClientName") = Request.Form("cmbClient") rsTimeSheet("BillCode") = Request.Form("cmbCode") rsTimeSheet("Hours") = Request.Form("txtHours") `Add the record rsTimeSheet.Update `Check for Errors If oDBTime.Errors.Count > 0 Then Set oError = oDBTime.Errors(0) If oError.Number <> 0 Then sResponse = "<H3>Timesheet Error </H3><BR><P>" sResponse = sResponse & "Error adding your timesheet to the Âdatabase." sResponse = sResponse & "Error: " & oError.Description End If Else sResponse = "<H4>Timesheet Added for " & Request.Form("txtWorkDate") & Â". Scroll to the bottom of the page to view previous time added.</H4>" End If End If rsTimeSheet.Close oDBTime.Close AddTime = sResponse End Function </SCRIPT> <HEAD> <Title>Active Server Page TimeSheet Application</TITLE> </HEAD> <BODY BGCOLOR="Tan"> <CENTER> <H2>Timesheet Application using Active Server Pages, ActiveX Components and ÂADO</H2> </CENTER> <HR SIZE=2> <!-- In this example, we are posting back to the same ASP file that --> <!-- submits the request. As such first parse the form to see if it has --> <!-- Been entered properly. --> <% If Request.Form("txtEmployee")="" Then sMsg = "Enter an Employee's name in the timesheet." Session("TimeAdded") = False ElseIf NOT (IsDate(Request.Form("txtWorkDate"))) Then sMsg = "You must enter a valid date in the date field." Session("TimeAdded") = False ElseIf Request.Form("txtHours")= "" Then sMsg = "You must enter a valid number of hours." Else Session("TimeAdded") = True sMsg = AddTime End If %> <!-- Declare the start of the form. --> <B><%=sMsg%></B> <P> <FORM NAME="TimeSheet" METHOD="POST" ACTION="SrvTimes.asp"> <B>Employee:</B> <INPUT TYPE="text" NAME="txtEmployee" Size=30 ÂValue="<%=Request("txtEmployee")%>"> <p> <B>Select the Client</B> <% `Create an instance of the ADO Recordset object set rsClients = CreateObject("ADODB.Recordset") `Open a resultset based on the SQL Statement rsClients.Open "SELECT ClientName FROM Clients","DSN=TimeSheet" %> <!-- Notice in the following section the mixture of HTML with server side Âscript --> <SELECT NAME="cmbClient"> <% Do While Not rsClients.EOF %> <OPTION><%=rsClients("ClientName")%> <% rsClients.MoveNext Loop rsClients.Close Set rsClients = Nothing %> </SELECT> <p> <B>Select the billing code</B> <SELECT ALIGN=CENTER Name="cmbCode"> <OPTION>Holiday <OPTION>Vacation <OPTION>Sick <OPTION>Client Server Consulting <OPTION>Work Group Consulting <OPTION>Internet Consulting Services </Select> <p> <B>Date:</B> <INPUT TYPE="text" NAME="txtWorkDate" Size=15 Value=<% =Date %>> <p> <% If Session("Hours") = "" Then Session("Hours") = "8.0" Else If Request("txtHours") <> "" Then Session("Hours") = Request("txtHours") End If End If %> <B>Hours:</B> <INPUT TYPE="text" Name="txtHours" Size=4 Value="<%=Session("Hours")%>"> <HR> <INPUT TYPE=SUBMIT VALUE="Submit Form"> <SCRIPT LANGUAGE="VBScript"> <!-- Sub window_onLoad() Dim CurrentForm `Set the focus to the text box Set CurrentForm = Document.TimeSheet CurrentForm.txtEmployee.Focus Set CurrentForm = Nothing end sub --> </SCRIPT> <% If Session("TimeAdded") Then `Create an Instance of the ActiveX component to send `back an Employee's time. Set oEmpTime = Server.CreateObject("ASPComp.GetTime") oEmpTime.GetEmpTime End If %> </BODY> <!--#include virtual="/ASPSamp/Samples/adovbs.inc"--> </HTML>
Review the ASP script in Listing 22.4 carefully. SrvTimes.asp is very different from the timesheet application created in Chapter 21. Like the ASP used in the previous example, which uses standard ActiveX components, SrvTimes.asp posts the form back to itself, so all the code is contained in a single script file. Unlike the timesheet example created in Chapter 21, though, SrvTimes.asp performs all form validation on the server, so you don't have to be concerned about whether the browser supports VBScript. The hours submitted in the application default to 8 hours unless a previous number is entered (the timesheet application in Chapter 21 always defaults to 8 hours).
The script required to use your ActiveX component to produce the HTML table is quite simple:
<% If Session("TimeAdded") Then `Create an Instance of the ActiveX component to send `back an Employee's time. Set oEmpTime = Server.CreateObject("ASPComp.GetTime") oEmpTime.GetEmpTime End If
Before wrapping up this chapter, you should take a look at a few remaining topics to consider when creating Visual Basic 5 ActiveX components to be used in Web pages.
As stated earlier, ActiveX components created with Visual Basic 5 use the Apartment Thread model and can be single or multithreaded. If you create an application- or session-scope object with a multithreaded ActiveX component that uses Apartment Model threading (created with Visual Basic 5), the object created will be only single threaded. For an application-scope object this could affect performance in a multiuser environment due to blocking.
The code base address of a DLL is the address in memory in which the code for your component is loaded into memory. Various processes can share the single memory copy of the code if there are no address conflicts. A conflict occurs when an in-process component's base address is being used by another in-process component or the executable. When a conflict occurs, the code must be dynamically relocated during the in-process load process, which slows down the component load process. The relocated code in memory for an in-process component generally cannot be shared by other processes.
The valid range of values for the base address is between 16MB (hexadecimal value 100000) and 2GB (hexadecimal value 80000000). The code in memory cannot exceed the 2GB range, so you need to obtain a code base that guarantees that the component's base address plus code size will not exceed 2GB. Base addresses are increased in multiples of 64KB (hexadecimal value 10000). The default address for ActiveX DLLs created with Visual Basic 5 is 285,212,672 (hexadecimal value 11000000). Never create components that use the default address assigned by Visual Basic, or all your ActiveX components created with Visual Basic will have conflicting base addresses.
To prevent components from having conflicting base addresses, you must develop a method to assign code base address that are not being used by other ActiveX components. You could create an application that generates and assigns random base addresses and store the assigned address and the component name in a database to be used for tracking and maintenance purposes.
To change the base address of a component, use the Compile tab of the Visual Basic
5 Project Properties dialog box shown in Figure 22.15.
FIGURE
22.15. The Compile tab of the Visual
Basic Project Properties dialog box.
When testing a new ActiveX component, use the default Project Compatibility option on the Component tab of the Project Properties dialog box. Project compatibility enables your test projects to maintain the component reference during various test sessions by reusing the type library identifier (new type library information is generated). When you are happy with the component and create a DLL to install on various machines, switch to binary compatibility. Binary compatibility ensures that programs using a previous version of your component will be able to use the new version of the component without a problem; this compatibility ensures that previously defined methods and properties remain intact. A message box appears if the interface to the component has changed in a way that will affect programs using previous versions.
You should test ActiveX components before using them on a Website. Test your components using a test program that creates objects from your components and uses the exposed methods and properties. To test components that take advantage of ASP and ADO, try to test on a PC that has Visual Basic 5 and Active Server Pages running. You can use a Windows 95 machine running Microsoft's personal Web server with Active Server Pages or a Windows NT 4.0 machine with IIS 3.0. Also, when replacing an in-process component that has been used (loaded into memory), you will have to stop the Web server to overwrite the previous version.
You can create ActiveX components to take advantage of your current Visual Basic skills and easily enhance your Web application's functionality. Generate ActiveX multithreaded, in- process components (DLLs). Avoid using OLEISAPI; instead, use ASP. You also can create ActiveX components that use ASP intrinsic objects to retrieve information from a posted form or to send information back to a browser client.