Chapter 14

Constructing Your Own Server Components


There are often times when you will want to use server-side functions that just cannot be accomplished through the use of a scripting language, either alone or in combination with the components that ship with Active Server Pages. When you hit this particular wall, you have a few options. First, you can check the market and try to find prebuilt components that satisfy your requirements. Second, you can contract with a third party to build a component for you. Last, and given the time and inclination, my favorite choice: You can build your own component.

When you create your own server components, you have the benefit of using a component built by someone who really understands your business. As you encapsulate line-of-business functions within your components, you can use them on your server and in your client applications or even give them to your clients for use on their systems (for a nominal fee, of course). This chapter focuses on creating server components using the Visual Basic programming language. If you already are familiar with VB, this will be a snap! If you are coming to VB a little later in the game, this chapter will provide you with the skills to begin creating components right away.

Why Use Visual Basic for Component Creation?

There are a number of reasons why Visual Basic is an ideal tool for creating many of the server components that you will use. VB has grabbed the hearts and minds of millions of developers out here in the real world. As you already have learned, VBScript is a subset of Visual Basic. Everything you've learned about VBScript is immediately applicable to development in the VB environment, with a number of useful features not found in its younger sibling.

You will find (or already have found) hundreds of custom controls, such as ActiveX Components, that currently are available for use with Visual Basic. More controls seem to be available each day. The rub is that most of these components require an interface to use them in your development. VB provides a perfect way to wrap the functionality of these third-party components for use in your Active Server Pages development. In addition to the custom controls available in Visual Basic, you also have access to the Win32 API. You can access any number of functions on the system that are impossible to get to using scripting alone. You also can use most any DLL (dynamic-link library) to add additional functionality to the components that you create.

As stated previously, millions of developers have used Visual Basic for a number of years because of its ease of use and flexibility and the speed with which they can develop applications. If you were to hire a new Active Server Pages developer, it is likely that he or she would have VB skills. If not, those skills are just a class or two away.

Another advantage of developing your server components in VB is that there are so many resources available to help you. There are forums on CompuServe, AOL, and Prodigy. The Microsoft Web site provides a wealth of information about VB (a knowledge base, news groups), as well as links to other valuable sites. There are hundreds of quality sites out there dedicated to Visual Basic.

Of course, another benefit of developing your components in Visual Basic is that there are so many excellent sources of information available to help you in your own neighborhood. There are hundreds of texts at your local bookstore with even more information about creating objects in VB than you'll find in this one chapter alone.

Finding A Touch of Class

Component creation is a process of building objects that you will instantiate in your Active Server Pages scripts. To create objects in Visual Basic, you need to define a blueprint to expose the functions within your object to other applications, in this case ASP. You will use classes to provide this blueprint, or definition and interface, for your objects.

Creating class modules in Visual Basic is the method through which you can develop reusable components. Public procedures within classes are the method through which you can expose functionality from within your class to other objects within your application and to other applications within the system. By creating your functionality within the class framework, you are able to harness the incredible power and ease of use in Visual Basic for your component needs.

Introducing Classes

As you begin to develop components, consider some of the following details of class development. A class is the description, or blueprint of the object that will be created when the class is instantiated. You can follow some basic guidelines from general object-oriented development to ensure that your class will be a good component candidate.

The idea of data-hiding or encapsulation, is a fundamental principle of object-oriented development. What this means in practice is that access to the data variables within your class should be performed only through member functions of the class. Effectively, you will be hiding the class data from the client program. This ensures that the client application will not unwittingly corrupt the Private object data. This also lets you change the Private members of your class "under the covers"-without changing the Public interface.

Think of your component in terms of properties and methods. The properties are a set of definitions that control attributes of your component. Methods are the actions that the component will take on your behalf. Look to the components that ship with Active Server Pages and Visual Basic and see how the properties and methods work together to provide the components' functionality.

Always strive for limiting the functionality of a component to a basic set. Don't try to put the entire business in a single component. Try to view component creation as an exercise in creating building blocks. If the blocks are too big, you lose flexibility in the design. If they are too small, it will take forever to build the structure.

Understanding Object Lifetime: Class Creation and Destruction

When the client application instantiates your object (class), there is an opportunity to perform one-time initialization actions for your class. You do this in the Class_Initialize event of the class object. This is a handy place to initialize Private data members and perform any initial error checking, as well as any other functions that need to occur before any of the class methods (functions and/or subroutines) are invoked.

There is also an opportunity to perform actions immediately before the object is destroyed. This processing is performed in the Class_Terminate event of the class. You can destroy any memory that was allocated during the life of the class or perform any other required cleanup.

Instantiating an Object

Once you have defined and coded the class, you can create an instance of the class in Visual Basic by initializing an object variable to that class type:

Dim TestClass as New ClassName

You also can instantiate a class object by using the CreateObject method within your script:

Dim TestClass

Set TestClass = Server.CreateObject("Component.ClassName")

The variable TestClass now has a reference to the newly created object. Now you can invoke any of the methods of the newly created object by using the method name with the newly created object reference:

Avalue = TestClass.MethodName(Parm1, Parm2… ParmN)

When you are finished with the object, set the object variable to Nothing to ensure that all memory associated with the object will be released.

Set TestClass = Nothing

Understanding Class Methods

When we refer to the methods of a class, we are talking about the set of Public functions and subroutines that reside within the class. Methods are just a convenient way to talk about the procedures available to the user of a component. This is also an easy way to think of these functions, like the methods available to any of the other "objects" within Visual Basic: the form methods, the button methods, and all of the other intrinsic objects in VB that expose their functionality (methods) to you as a developer.

Any subroutine or function that you create in your class module will be visible to the client program if it is declared as Public.

Public Function CalculatePayment(LoanAmount, Term, InterestRate) 

When you use the Public identifier in front of the function declaration, the CalculatePayment function is visible to any client that instantiates the class or to any variable that is assigned a reference to it. Private functions within a class are used to provide functions and services that the Public functions can use to fulfill client requests.

Using Property Procedures

In Visual Basic development, as well as HTML development, user interface objects have properties that you set to determine the look and actions of these elements. For example, to access the background color of a text box in the VB environment, you would access its BackColor property:

text1.BackColor = &H00FFFF00&

The use of properties is a simple and intuitive way to access the attributes of an object. You can provide the same functionality to access the properties of your components. This will be a familiar syntax to VB and Active Server Pages developers and will let you use the <object>.<method> notation with any component that you create. Using property procedures also will let you immediately validate the value of the property being set. If invalid data is passed in or a value out of range is provided, the component can respond immediately, instead of waiting for some point downstream in your code to notify the calling program of the error.

Once you have determined what properties you want to provide for your class, you will set aside Private variables to hold these properties. You then need to provide the framework for accessing and updating these class properties.

The Let Procedure

The Let proedure enables your class users to set the value of a class property under the class's control. Take a quick look at a Let property procedure:

Public Property Let HostName(aHost)

   If Not Len(aHost) Then

      Err.Raise vbObjectError + CTRANSACT_ERR_HOSTLEN, "CTransact.Host", _

                "Set Host Name: Host Length Invalid"

   Else

      m_sHost = aHost

   End If

End Property

You certainly could declare the Private class variable m_sHost (declared as a string) as a Public variable and then let the user set the host name directly. If you did that, you wouldn't have the opportunity to ensure that the host name was valid. Within the Let procedure in the preceding code, in addition to testing for a zero length string, you could also try to ping the host to ensure that it was a valid address or perform additional validations on the passed-in value. When using the Let procedure, you can provide the calling application with immediate feedback as to the status of the component property being set.

In addition to allowing for data validation when the property is set, you also can update other associated variables within your class. If there is another property, say the port to connect to, that is based upon the host that is entered, you can set the port property when the host name is set. This again cannot be performed if you just declared the m_sHost variable as Public. Using the Let procedure can ensure that you don't get any component breaker values in the Private variables of your class. If, for example, a user were to put a NULL value into a variable, it could easily generate a run-time error in another method that references that variable. The rule continues to be "better safe than sorry."

The Get Procedure

The majority of your coding will set property values of the objects created within the script. Most of you could go the better part of a day without ever requesting the value of an object's property, but there are times when you need to check a property and determine its value. A good example would be the .text property of an entry field.

To provide the "getting" of a property within your class, you create a Property Get procedure. Using the preceding example, you would provide the HostName property procedure to your calling application with the following procedure:

Public Property Get HostName() As Variant

   HostName = m_sHost

End Property

Public Procedures in Your Classes

The Let and Get procedures are wonderful for creating properties within your class. Eventually though, you need your class to perform some function in order to become useful. To expose functionality to an external application from within your class, you declare a function or subroutine Public. This enables the application that created an instance of your class to access the procedure.

Any procedure that is not directly used by the client application should be declared Private. This ensures that the client does not inadvertently call a function or subroutine that it should not. Private functions are used to perform internal activities for the component. As you will see in the component example, "Creating Your Server Component," the Public interface usually is the smallest part of the class code. The component interface is intentionally kept small and simple. If the implementation of the method changes (the Private functions), there is no need to change the external (Public) function declaration.

Handling Errors Within Your Classes

There are two ways you can handle errors within your classes. The first requires you to provide all methods as functions and return a completion code for all Public procedures. This requires a well-defined set of return codes and requires the client program to check for errors in-line after each call to the component returns.

A better way to handle error conditions within your class is to use the VB error-handling framework. If an error occurs within a class module, you will use the Raise method of the Err object to pass notification of the error to the calling program. The error percolates through the procedure levels to a place where an error handler has been registered within the calling program.

You notify the client application of an error by calling the Raise method of the Err object. The syntax for raising an error is shown here:

Err.Raise(Number, Source, Description, HelpFile, HelpContext)

The only required parameter is the Number. When you are creating server components, you usually should include the Source and the Description parameters as well. Say that you are trying to open a local file in a method in your new class. The file cannot be opened because the program cannot find the file on the disk. A run-time error that the component invoked in your code looks something like this:

Err.Raise vbObjectError + 55, "CFileMgr.ReadFile ", "Cannot find file specified"

The constant vbObjectError is a base number to add your component specific error number to. This is the constant that is used when raising run-time errors within an OLE server.

See "Error Handling in Procedures" for more on error handling within your scripts, in Chapter 9.

OLE Servers-Exposing Your Class

Up to now, a number of pages in this chapter have been spent discussing the class module and how it provides the blueprint for your Active Server Pages when your component is invoked. This is well and good, but components live by more than class alone. You need to wrap the class in a suitable way so that you actually can create an instance of it. To do this, you create the class within an OLE server.

There are two types of OLE servers that you can create with Visual Basic. OLE servers can run either out-of-process or in-process. The distinction is basically evident in the server names. An out-of-process server executes in a separate process from the client application that creates an instance of the server (component). An in-process server is packaged as a dynamic-link library and, when instantiated, shares the same process space with the application.

An in-process server has a number of inherent strengths that ensure that it is the type you will build when creating your server components. First, because the server will be running in the same process space as the calling application, the component will be faster, because there is no need to pass data across process boundaries. Second, because the server is packaged as a DLL, if it is already loaded in memory when a new instance is created, there is virtually no load time for the new object.

There are, however, a number of restrictions to keep in mind when creating in-process servers as components in Visual Basic:

Creating Your Server Component

It's time to begin creating your Active Server Pages component. It's useful to take a moment and discuss what it is, exactly, that your component will do for you. Many of you are working in heterogeneous environments, interacting with PCs, minis, mainframes, and, yes, even UNIX boxes. The component that you are going to build will provide access to a number of transactions living on a multiple host systems.

Here's the scenario: Say that for a number of years, your hypothetical company has been processing TCP/IP requests from external vendors to authorize credit card transactions. You have a well-defined transaction header structure that is used to send a transaction through the system. You want to provide the same authorization transactions from your Web servers.

There are a number of commercial packages out there (even Microsoft's own Internet Control Pack) that provide generic TCP/IP communications. The problem with most of these packages is that they are set up to provide an event when a transaction (or any data, for that matter) is received back from a previous request. By design, they are asynchronous, meaning that the receipt of the response from the transaction occurs some time later. The program or thread execution will not block waiting on a response from the server. This is ideal for interactive client/server applications where you are doing other things while waiting for a response (or multiple responses). Due to the stateless nature of connections on the Internet, there is no facility to continue to process the script and then respond to this "transaction complete" event later.

So what you are going to build is a TCP/IP transaction class that lets you call a method that sends a transaction to your host system and waits for a response before returning from the procedure call. All formatting of the transaction takes place within the class, as well as all of the communications details. The only things that the script must provide are a host name and port to connect to, an account to validate, and an amount to authorize. We're sure you can think of a number of situations in which you would want to leverage TCP/IP transactions from your servers. This component can easily be modified to accept different header and transaction types. Out-of-the-box, it should give you a good sense of the steps involved in building your own server components.

First Steps

The first thing you need to do is ensure that you have a copy of the 32-bit version of Visual Basic, either the Professional or Enterprise Edition. Then open up a new project to begin creation of your transaction component.

It's that easy! Now you are ready to begin.

Slimming Down the Server

When you start a new project in Visual Basic, there are a number of custom controls and object references that get added to your project for free, or they seem so at the time. In fact, they are not free at all. If you open the file AUTO32LD.vbp (found in the directory where the VB32.exe lives on your system) in Notepad, you see the default objects that are loaded each time you request a new project. To permanently change these defaults, you can edit this file to remove the object references.

Any component or reference that you do not use but do retain in your project when it is built, tags along for the ride, even though it never is invoked. This adds overhead to your application and swells the number of disks required to distribute your new component.

So now you will remove all the custom controls that you can from your newly created default project to reduce this overhead:

  1. Select Tools, Custom Controls or press Ctrl+T.
  2. Select Tools, References.
  3. Once Form1 is selected, select File, Remove File from the main menu. This removes the form from the project.

The first step brings up a dialog box with all the controls available for your use, shown in Figure 14.1, as well as those currently active in your project. Those that are active will have the check box checked. Remove all the currently selected objects by removing the check in any checked box. Then click the OK button to save your changes.


Figure 14.1

Lighten up the application by removing unused controls.

The second step brings up a dialog box with all the references available for your use, in addition to those currently in use for the project. If you are not going to be performing any database functions using the Jet database engine, remove the reference to the DAO library. This cuts your distribution down by over a meg. If there are any other references that you will not need, remove them as well by unchecking the checked boxes.

There are no user interface attributes in your server component, so remove the default form that loaded in your new project. To remove Form1, as outlined in step three, select the form from the project window. If the project window is not visible, press Ctrl+R to show it.

Main Street: The Sub Main Function

While all functionality that you will expose to your client application-in this case, an Active Server Pages script-is through the Public methods of a class, there still is component-level initialization that can take place when the component is created. Component-level initialization takes place in the Sub Main procedure of the component.

To add the Sub Main procedure to your new project, you need to add a code module. To do so, follow these steps:

  1. Select Insert, Module from the main menu.
  2. Press F4 to pull up the module properties.
  3. Change the name Module1 to CTransact_Main.
  4. Move back to the newly named CTransact_Main code window.
  5. Type in Sub Main, and then press Enter.

These steps add a new module to the project. The module defaults to Module1 in the project window. You are creating a component that will provide transaction processing for your server, and this component will be called CTransact, so you will call the module with Sub Main in it CTransact_Main.

You now have created an empty Sub Main procedure in the CTransact_Main module:

Sub Main()

End Sub

Because you will perform no component-level initialization in your transaction component, you can leave the Sub Main procedure empty. Even though no explicit initialization takes place, you must have a Sub Main procedure defined in your component, or you will receive an error when you try to instantiate the component.

The Project Dialog Box

Now is a good time to save your project. Before you do that, though, set up the project so that the compiler creates an OLE in-process server instead of a normal Windows executable program. The settings regarding the code generation are found on the Options dialog box. To get there, follow these steps:

  1. Select Tools, Options from the main menu.
  2. Select the Project tab on the dialog box.

The menu options for Visual Basic 4.0 specified in the steps defined within this chapter may change in version 5.0.

As shown in Figure 14.2, you will notice a number of user-definable options for the project. The first thing you need to do is specify the Project Name. The Project Name will be the name you will use when specifying an object to create within your Active Server Pages script.

  1. Enter the name CTransact in the Project Name field.

    The StartMode setting specifies whether you will build a normal Windows executable file or an OLE server. Of course, you want to build an OLE server.

  2. Select the OLE Server radio button.

    The last field that you will edit on the project is the Application Description entry field. This will be the text that you will see when you browse the objects and components currently available on a system. You want to provide a short and concise description of your component.

  3. Type TCP/IP Transaction Black Box in the Application Description field.
  4. Before you leave, jump over to the Advanced tab and select the Use OLE DLL Restrictions check box. This ensures that, as you debug your application, the same restrictions placed on an in-process OLE server will apply to your component during debugging.
  5. Click the OK button to save the project changes that you just made.

Figure 14.2

The Project tab defines your component naming.

  1. Select File, Save Project from the main menu.

    You will be prompted to save the CTransact_Main.bas module in the default directory. Accept the default directory or create a new directory from the Save As dialog box, and then save the module. When prompted for the project name to save, replace the Project1 name with CTransact.

Congratulations! You have just completed the first task in creating your component.

Creating Your Transaction Class

Now the real work begins. Up to now, you have been dealing with the administration of creating a server component, setting up the project, selecting project options, and creating a main procedure. Now you begin to develop the class data and methods that will perform the transactions that you have been working toward.

The first step is, of course, to create the class module. The class information will live in its own class code module, a file with a .CLS extension. To create the class module, select Insert, Class Module from the main menu.

Now you've added a new class module to your project. If the class module is not currently selected, select the module from the project window by double-clicking the name Class1 or by selecting Class1 and then clicking the View Code button of the project window. Remember that if your project window is not visible, you can pop it up using Ctrl+R.

In the next few steps, you will rename your class, set it to be Public, and select the instancing options. With the class code window active, press the F4 key to bring up the class properties window. It will look like the one shown in Figure 14.3.

  1. Change the name Class1 to Transaction.
  2. Set the Public property to True.
  3. Set the Instancing property to Creatable MultiUse.

Figure 14.3

Editing class properties is just like changing any VB object's properties.

The first of the preceding steps, changing the name, is important, because the name of the class is referenced when you create an instance of your component. Just as you take care in naming your class methods, you need to make a good choice for the class name.

The Public property determines the visibility of the class outside your OLE server. If you were to leave the class private, no one could access any of the transaction functions that you will build into the class shortly.

The last property of your class that you set was the Instancing property. This property determines how the OLE server is managed when it is instantiated by a client application. You selected the Creatable Multi-Use property, which enables the object, if already created, to be supplied by the existing object. If no object currently exists, one will be created. Contrast this to the Creatable Single Use option, where each CreateObject request causes a separate copy of the OLE server to be started. The Creatable Multi-Use option uses memory more efficiently and is the primary choice for externally created components like the one you are building.

The WinSock Methods

To perform TCP/IP transactions, you will be using the Windows Sockets library calls in this section. One of the nice things about Visual Basic is that you can declare just about any dynamic-link library and use it in the VB environment. To use these WinSock functions within your component, you need to declare them to Visual Basic. This is done with the declare statements shown in Listing 14.1, in the general code of the Transaction class.

Listing 14.1 TRANSACTION.CLS-Declaring the WinSock Functions to Visual Basic

'Winsock calls in VB format, for 32 bit, WSOCK32.DLL

Private Declare Function bind Lib "wsock32.dll" (ByVal s As Long, _

                                  addr As sockaddr_type, _

                                  ByVal namelen As Long) As Long

Private Declare Function inet_addr Lib "wsock32.dll" (ByVal s _

                                  As String) As Long

Private Declare Function gethostbyname Lib "wsock32.dll" _

                                 (ByVal hostname As String) As Long

There are a number of other Win32 API functions that you will be using in your class. You can find all the declarations for these functions in the TRANSACTION.CLS module, in the declarations section.

There is also a number of helper procedures within the class that handle the dirty work of the TCP/IP communications. Generally, the flow for the transactions follows this path:

  1. Connect to the host.
  2. Send the transaction to the host.
  3. Receive a response.
  4. Check the return code of the response.
  5. Pass the response back to the calling application.

Each of the preceding steps has a Private procedure that handles one part of the communications chore. Listing 14.2 shows the code for the fn_Connect function, which connects to the host system. All the helper procedures do one activity and return a Boolean value if the activity is completed successfully.

Listing 14.2 TRANSACTION.CLS-The Connect Function

Private Function fn_Connect(iSocket As Long, lAddress As Long, iPort As Integer) As Boolean

   Dim sockaddr As sockaddr_type

   Dim rc As Integer

   

   sockaddr.sin_family = AF_INET

   sockaddr.sin_port = htons(iPort)

   sockaddr.sin_addr = lAddress

   sockaddr.sin_zero = " "

   

   rc = connect(iSocket, sockaddr, Len(sockaddr))

   If rc = SOCKET_ERROR Then

      Err.Raise vbObjectError + CTRANSACT_ERR_CONNECT, 

                "Transaction.fn_InitSockets", fn_GetLastSockError()      

      Exit Function

   End If

   

   fn_Connect = True

End Function

If the connection fails, an error is raised, passing back the error code as well as the text describing the reason for the connection failure. The fn_GetLastSockError function returns the error text for the most recent socket error, utilizing a call to the Windows Sockets extension function WSAGetLastError(). For additional information about the WinSock 1.1 specification, check out the Microsoft Web site at http://www.microsoft.com.

Private Class Variables

The first thing you need to do after setting up the declarations for WinSock and Win32 API functions is to create your Private class variables, as shown in Listing 14.3. They are dimensioned as Private so that your client does not have access to them, and cannot accidentally change one of their values at an inopportune time.

Listing 14.3 TRANSACTION.CLS-Declaring Class Variables Private

'Module Level Variable Declarations

Private msHost As String              ' Holds the host name

Private mlHost As Long                ' 32 bit host address

Private miPort As Integer             ' Host port to connect to

Private miConnectTimeout As Integer   ' connect timeout

Private miReceiveTimeout As Integer   ' receive timeout

Private msBuffer As String            ' buffer to hold transmitted data

Private mbInProcess As Boolean        ' in process flag

It is great that you have all these Private variables to use in your class, but you also need to get some pertinent information from the component user so you can send your transaction on to the correct host. In the section "Using Property Procedures", we talked about property procedures that enable your users to set properties within your component. Two properties that must be set for you to perform your transaction are the Host and Port properties. These will provide you with the name of the host to send the transaction to, as well as the port to which the host will be listening. The property procedure for the Host property is shown in Listing 14.4.

Listing 14.4 TRANSACTION.CLS-Property Procedures Enforce Data Hiding

Public Property Let Host(aHost As String)

   If Len(aHost) = 0 Then

      Err.Raise CTRANSACT_ERR_HOSTLEN, "Transaction.Host", "Set Host Name: Host Length Invalid"

      Exit Property

   End If

   mlHost = fn_GetHostByName(aHost)

   If mlHost = 0 Then

      Err.Raise CTRANSACT_ERR_HOSTBYNAME, "Transaction.Host", fn_GetLastSockError()

      Exit Property

   End If

               

   msHost = aHost

End Property

Notice in the code in Listing 14.4 that you are handling errors by raising run-time errors that your component user will catch in his or her code. If the Host length sent in by the application is zero, you will raise an error. Also, if the host name cannot be resolved to a physical address (the fn_GetHostByName function provides the name resolution services), an error is also raised. If you were setting properties just by allowing your component user access to a Public variable sHost, you would have to check for a valid host name each time you performed a transaction. By handling this check through the use of a property procedure, you can raise the error if needed immediately when the property is set.

The Public Transact Method

There is only one Public procedure in the CTransact class. This is the procedure that initiates the transaction and returns a completion code to the calling program. The function must be declared as Public so it will be visible from outside the component.

The first part of the method (or procedure) is the declaration. Parameters are passed to the method and a return code of type Long will be sent back when the transaction is completed.

Public Function Transact(transaction As Integer, Version As Integer, _

        inBuffer As String) As Long

There are a number of authorization transactions that can be processed by the back-end service. Also, to support new versions of transactions as time goes on, a version is passed in as a parameter as well. This is a handy way to enhance transactions while not having to change any of the clients currently using a previous version. The buffer that is passed in is the authorization string formatted by the Active Server Pages script.

The first part of the code, found in Listing 14.5, ensures that a valid Host property has been set, as well as a value for the Port property.

Listing 14.5 TRANSACTION.CLS-Verifying that Required Class Properties Have Been Set

If miPort = 0 Then

      Err.Raise CTRANSACT_ERR_PORTNOTSET, "CTransact.Transact", _

                "Port must be set prior to invoking Transaction method"

      Exit Function

   End If

   If mlHost = 0 Then

      Err.Raise CTRANSACT_ERR_HOSTNOTSET, "CTransact.Transact", _

                "Port must be set prior to invoking Transaction method"

      Exit Function

   End If

End If

You check for a valid host when the property is set, but you must also ensure that the property has been set before you begin the transaction. You could have added another Private variable as a flag, say blnHostSet, but it is just as easy to check the value of the miPort and mlHost variables. It also saves a bit of memory (the less module-level variables you use, the better).

The interaction with the host system is through a standard header that is defined by the type TRAN_HDR. All of the transactions are performed using this header, and the return code from the transaction will be put in the ReturnCode member of the TRAN_HDR type. Ideally, you would send the structure in the transaction. In the C or C++ programming languages, you would just cast the structure variable as a char * or a void * and be done with it. In Visual Basic, you need to send the transaction as a string. To do this, we ended up creating a little C DLL to perform the conversion from a string to a type and back again.

ON THE WEB

http://www.mcp.com/que/asp—At this book's Web site, you'll find the source code for VBUTIL.DLL and the project files to re-create it using Microsoft's Visual C++ product.

The next part of the code fills in the TRAN_HDR type, converts it to a structure, and prepares to send the transaction over the wire (see Listing 14.6).

Listing 14.6 TRANSACTION.CLS-Using the VBUTIL CopyStructToString Function to Simplify the String Conversion

   hdr.PacketNumber = Format$(transaction, "00000000")

   hdr.Version = Format$(Version, "0000")

   hdr.ReturnCode = "9999"

   hdr.OperatorNumber = ""

   hdr.RecordLength = Len(hdr) + Len(inBuffer)

   

   msg = Space$(Len(hdr))

   rc = CopyStructToString(hdr, msg, Len(hdr))

Notice that you must pre-allocate the msg (which will host the string representation of the TRAN_HDR type) string by filling it with spaces equal to the size of the header into which you are going to copy it. If you forget to pre-allocate the msg string, you receive an error.

Now comes the heart of the communications functions. All the TCP/IP functions have been created for you as Private procedures in the class, and calling them within the Transact method is shown in Listing 14.7. As each function is called, the function returns codes that are checked after the call is made to ensure that you are still communicating with the host system.

Listing 14.7 TRANSACTION.CLS-The Bulk of the Communication Is Transparent to the Transact Method

socket = fn_OpenSocket()

   If socket = 0 Then

      Err.Raise CTRANSACT_ERR_OPENSOCKET, "CTransact.Transact", fn_GetLastSockError()

      Exit Function

   End If

                  

   If fn_Connect(socket, mlHost, miPort) Then

      If fn_SendData(socket, msg & inBuffer) Then

         msBuffer = fn_ReceiveData(socket, 60)

         If Len(msBuffer) Then

            rc = fn_CloseSocket(socket)

            rc = CopyStructToString(hdr, msBuffer, Len(hdr))

            retCode = hdr.ReturnCode

            ' remove rich header from data buffer

            msBuffer = Mid$(msBuffer, Len(hdr) + 1)

         Else

            Exit Function

        End If

      Else

         rc = fn_CloseSocket(socket)

         Err.Raise CTRANSACT_ERR_SEND, "CTransact.Transact", fn_GetLastSockError()

      End If

   Else

      rc = fn_CloseSocket(socket)

   End If

There are a couple of return code type variables used in the Transact method. The first, retCode, holds the value of the returned code from the host transaction. This is a four-character string variable. The second, rc, is an integer that holds the transaction specific return code that is sent back to the calling program. Once you receive the return code (retCode) from the host, you interrogate it, as shown in Listing 14.8, to return the appropriate value to the calling application.

Listing 14.8 TRANSACTION.CLS-Formatting the Return Code for Your Calling Application

'Based upon the return code from the transaction, pass a value

   'to the calling app

   Select Case Left$(retCode, 2)

      Case "00"

         rc = TRANSACT_RC_SUCCESS

      Case "IL"

         rc = TRANSACT_RC_INVALID_ACCOUNT

      Case "IR"

         rc = TRANSACT_RC_INVALID_TRAN

      Case Else

         rc = TRANSACT_RC_SERVER_ERROR

   End Select

   

   Transact = rc

Compiling the Component

The last step in building your transaction component is to generate the DLL. In the secion "The Project Dialog Box," you set the project options to generate an OLE server. Now you just need to instruct the compiler to generate an OLE DLL:

  1. Select File, Save Project from the main menu.
  2. Select File, Make OLE DLL File.

That's it! You now have successfully built your first Active Server component. Of course, you can use this component with Visual Basic, Access, Excel, or any other application that supports OLE components. The new component was registered automatically for you on the system where it was initially created.

Using Your New Server Component

Now that you have built the component, you need to create the form and Active Server Pages script to invoke that component. If you are going to use the component on a machine other than the one you created it on, you need to register the component on that machine.

Registering Your New Object

When you create the OLE DLL file in the Visual Basic environment, the component is registered automatically on the machine where it is compiled. If you want to move the DLL and associated support files to another machine, you need to register the new control after you place it on the system. There are a couple of ways to do this. If you are going to distribute your component, you can create an installation program using the VB Setup Wizard or another third-party installation package. The control will be registered during the installation process.

You also can just move the files to the new machine and register the control using the REGSVR32.EXE program that ships with Visual Basic. You can find the registration application on the Visual Basic CD-ROM in the \TOOLS\PSS directory. Here's how to register the control using the REGSVR32 program:

  1. Move the component DLL, and any supporting files (VB40032.DLL for example) to the new machine.
  2. Copy the REGSVR32.EXE file to a directory that is in the path on the target system (for example: \WINDOWS\SYSTEM).
  3. Switch to the directory where you copied the DLL file.
  4. Type REGSVR32 COMPONENT.DLL.
  5. When the component has been registered, you receive a "successfully registered" dialog box. Click OK to dismiss the dialog box.

Testing the Component

The easiest way to initially test your new component is to start another instance of Visual Basic, create a simple form, and create an instance of the component. Then you can set the properties and call the Transact method. This lets you fine-tune the component before you try to integrate it into your Active Server Pages scripts.

To test the component, start another instance of Visual Basic. Add a command button to the Form1 that's displayed at startup. Double-click the newly created command button to bring up the code window. Now type the following code found in Listing 14.9 into the code window to test your new component.

Listing 14.9 form1.frm-Test Script for the New Component

Private Sub Command1_Click()

   Dim tran As New Transaction

   On Error GoTo COMMAND_ERR

   

   tran.Host = "localhost"

   tran.Port = 7511

   rc = tran.Transact(1000, 0, "TEST TRAN")

   MsgBox rc

Exit_Sub:

   Exit Sub

   

COMMAND_ERR:

   MsgBox "Error: " & Err.Number & " " & Err.Description

   Resume Exit_Sub

End Sub

The only action left to take before testing your component is to add a reference to it in your new project:

  1. Select Tools, References from the main menu.
  2. Scroll down the list and check TCP/IP Transaction Black Box.

While testing, it's helpful to force errors to ensure that error handling within the component is functioning correctly. For example, you can comment out the line tran.Host = "localhost". This generates an error because, as you may remember from the HostName property procedure code found in the section "The Let Procedure", the host must be set prior to calling the Transact function.

See "Checking the Routine" for more information about methods of testing, in Chapter 9.

The error will be raised in the component and caught in the calling app by this directive:

On Error GoTo COMMAND_ERR

In your Active Server Pages development, you will not be popping up message boxes on your server, but at a minimum, you surely will want to log any communication errors for further study and return an appropriate response to your client.

Component Testing on the Net

To test the component on your server, you need to build a simple form that calls an Active Server script and passes the appropriate parameters to it (account number, authorization amount). Then you can create your transaction object, call the Transact method, and return the results to your client.

ON THE WEB

http://www.mcp.com/que/asp—The HTML form code and the Active Server Pages script to process the authorization request can be found on this book's Web site. (The assumption is that you might not want to read through another bare-bones HTML forms lecture at this juncture.) All of the Visual Basic code for the component, as well as the CTRANSACT.DLL, are on the site. We also included the source code and DLL for the VBUTIL.DLL functions.

From Here...

We finished the section on working with Active Server Pages objects and components with this chapter on building your own components. You have walked through the steps to generate your own components in Visual Basic. You now have all the tools and knowledge to create an infinite variety of applications for your Internet/intranet site using custom components. In Part IV, you will focus on database management and providing active content with live links to your back-end database systems.

Here's what to look forward to in Part IV:


© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.