COM+ to .NET: Fast Forward Your VB Components

Originally published at O’Reilly

The joke goes: “How do you pronounce ‘C#’?”, and the punch line is: “The language formerly known as Java.”

Extending this, you can say that .NET is “…the infrastructure formerly known as COM/DCOM.”

The .NET infrastructure is significantly different from COM/COM+. However, information about .NET has been out long enough so that most of you have passed the “But, why?” stage. You’re probably now interested in discovering how you can continue to create and support your existing component development to be ready when your organization adopts the production release of .NET. In particular, if you work with ASP and make use of COM+ components created with Visual Basic, you’ll be interested in how you can ensure that your components migrate well to the new .NET environment.

We’ll take a look at some of the changes .NET will have on your current VB ASP component development efforts. In particular, we’ll look at what you can do now to ensure your components are forward compatible with .NET.

What Variant?

One significant change between COM/COM+ and .NET is that the Variant data type is no longer supported; its functionality has been incorporated into the Objectdata type. For Visual Basic programmers this is a major change, particularly if you’ve worked with component development and have used variants to pass data to and from component methods.

For future compatibility, you can begin to use specific data types rather than the Variant in your new components. However, if you have used Variant in existing code, be aware that Microsoft has provided two means of porting your components that contain variants to .NET.

The first is the Visual Studio Upgrade Wizard, which is automatically engaged when you open your older VB code into Visual Studio .NET for the first time. This wizard replaces all instances of Variant with Object, and any time you try and type in a Variant, it replaces it immediately with Object. No additional effort is necessary on your part.

For instance, if you had a subroutine such as the following:

Sub test (ByVal varValue As Variant)

The upgrade wizard changes the subroutine to:

Sub test (ByVal varValue as Object)

If you didn’t provide a data type for a variable in VB 6.0, defaulting to a data type of Variant, the upgrade wizard automatically redefines the variable as Object in VB.NET.

(The upgrade wizard performs other upgrade modifications that will be highlighted throughout this article.)

You don’t have to open and convert a compiled component to .NET to ensure compatibility with the environment. A second means of porting your components to .NET is the utility, TlbImp. How to use this tool was discussed and demonstrated in a previous article; basically what happens is the tool creates a .NET assembly for the component that allows it to run within the .NET managed code environment, without having to recompile the component.

Dropping Variant isn’t the only change from VB 6.0 to .NET. VB Data Types and type checking is another significant area of change, which is discussed next.

Data Types and Data Typing

A chief concern about Visual Basic in the past, particularly from C++ or Java programmers, has been that Visual Basic is a weakly typed programming language. For instance, you can concatenate a number with a string in Visual Basic 6.0, and VB will automatically convert the numeric into a string value, using implicit conversion, as shown in the following example:

iValue = 123
StrSum = iValue & "123"

This type of operation is still allowed with .NET when using the overloaded concatenation operation (‘&’), as shown above, but any other use of numerical values where a string is expected will generate an error. As an example, you won’t be able to pass a number through a string parameter and have VB automatically perform the conversion:

Sub tstValue (String strValue As String)
...
Dim iValue As Integer
iValue = 13
tstValue iValue

The above works with VB 6.0, but it won’t work with VB.NET.

To support this new shift to stronger data typing, Visual Basic .NET also introduces the Option Strict statement:

Option Strict

Like its earlier predecessor, Option ExplicitOption Strict enforces explicit variable declaration:

Dim iValue As Integer

However, Option Strict enhances this data protection capability by prohibiting implicit conversions between strings and numbers, as well as limiting numeric conversions to those termed as widening conversions. A widening conversion is any that converts a smaller number, or less precise one, to a larger, more precise one, such as converting an integer to a double.

Option Strict also prohibits late-binding. Using this option, the following, while legal in Visual Basic 6.0 will generate a compiler error in .NET:

Dim obj As Object
...
obj.SomeMethodCall ...

As late-binding is detrimental to any wire based communication between application components, such as in a distributed or Web-based environment, you’ll want to avoid its use even within your existing Visual Basic 6.0 component development. Late-binding always requires two method calls for every call within the actual code: one to find information about the method (through automation and a process referred to as reflection), and one to invoke the method itself, necessitating two round-trips across the wire. Placing Option Strict at the top of your code module just helps to ensure that you don’t accidentally leave late-bound variables in your code.


Accessing COM/COM+ Objects Within the ASP.NET Environment–Don’t miss Shelley Powers’ first article on the differences between ASP.NET and the original ASP server environment. Shelley has also updated her book on ASP components within the Windows 2000/COM+ environment, Developing ASP Components, 2nd Edition.


All of these new techniques enforce a stricter adherence to a more strongly typed language, while still providing the option of more loosely typed conversions that do exist in many existing Visual Basic applications. This is a necessary compromise between the Visual Basic of today and the one included as part of .NET.

To ensure that your code moves cleanly into this more strongly typed environment, you can use the Option Explicitstatement now to enforce variable declarations in your current applications. Also, eliminate any narrowing conversions (converting numbers of larger precision and size to smaller data types, with the potential loss of data) in your code, and make use of explicit conversion functions when working with numbers and strings together:

StrSum = "Account is " & CStr(iValue) & " overdue"

In addition, use type libraries (included in your code module by attaching the libraries to your project) to perform early binding, not only for future compatibility, but also to improve the performance of your Visual Basic applications today.

When you open an existing VB 6.0 project in VB.NET, the upgrade wizard automatically adds the Option Strictstatement with a value of “Off” to provide for backwards compatibility in behavior. The Option Explicit statement is left as is (“off” by default).

Another .NET change to Visual Basic — and one that particularly impacts component developers — is the size of the integer data type, covered in the next section.

Integer Size

A significant data type change between the two releases of VB is the size of integer values. Starting with Visual Basic .NET, the Integer data type is equivalent to the new .NET System.Int32 data type, and is now 32-bits, and signed.

An advantage to 32-bit integers is that this size is more efficient within numeric operations than the older 16-bit integer, particularly on 32-bit operating systems, such as the current default Windows 2000 basic operating system.

Another benefit of the changed integer size is that VB is brought in line with other mainstream programming languages, such as C++, Java, and Perl. If you’ve worked with component development, particularly with components based in different languages, one of the most frustrating aspects of working with Visual Basic has been its 16-bit integer, especially when so many other languages have 32-bit integers. This is particularly apparent when accessing VB components from within PerlScript.

If you still want access to 16-bit numbers, you’ll need to use the new Short value, and use the Long data types for 64-bit values.

The upgrade wizard maintains the integrity of the VB application by converting VB integers to shorts (16-bit). If you want to ensure that your component is converted to the new .NET integer type, start using the VB 6.0 long data type for your variables and particularly your method parameters in your existing development effort.

A less significant data type change is that currency is no longer a supported data type. Instead, a new Decimal type has been provided to allow for more precision and more digits:

Dim decValue As Decimal

In Visual Basic 6.0, the Decimal data type was only available as a Variant sub-type.

Dates are also handled differently in Visual Basic .NET, and are implemented with the .NET DateTime format instead of the current Double format. To convert between the older and the new date format, Microsoft is providing routines such as ToDouble to convert the new data type to the original double value.

You can best prepare your existing code for these new data types by isolating your use of Date and Currency within your components as much as possible in order to manually migrate your code in the future if necessary.

Speaking of function parameters, another significant change within Visual Basic .NET is subroutine semantics, including a different parameter passing implementation.

Subroutine Modifications

Parameters, and how they are passed from the calling program are handled differently between VB 6.0 and VB.NET. By default in Visual Basic 6.0, all parameters are passed by reference if the ByVal keyword isn’t used. This allows the procedure to modify the variable used to pass the parameter in the calling program, which can lead to unexpected results, an example of which is:

Sub testValue(iNewValue As Integer)
   iNewValue = iNewValue + 20
End Sub

...

Dim iValue As Integer
iValue = 2

Call testValue(iValue)

' value is now 22 rather than 2

Parameters are passed by value as a default in Visual Basic .NET, protecting against accidental and unexpected data changes. You must explicitly use the ByRef keyword in order to pass a parameter by reference.

You can easily ensure the future compatibility of your existing code with Visual Basic .NET by using the explicit ByVal and ByRef keywords with all of your parameters in your 6.0 Visual Basic code.

The Optional parameter keyword also has different semantics in Visual Basic .NET. In 6.0 you can specify that a parameter is optional without having to provide a default value:

Sub someRoutine (Optional ByVal iValue As Integer)

In 6.0 you’d use IsMissing to check to see if the value changed. In Visual Basic .NET, you must supply a default value for all optional parameters:

Sub someRoutine (Optional ByVal iValue as Integer = 0)

To ensure smooth migration to .NET, when using Optional in your existing code, provide default values. This approach not only allows your code to move forward into .NET, it’s also a good programming practice to apply today – the use of IsMissing adds unnecessary complexity to your procedures.

Other changes with Visual Basic .NET have to do with structured data, such as arrays.

Structural Changes with Arrays

If you use arrays, you’ll find that you can no longer re-define the lower bound of an array with the Option Base statement in VB .NET:

Option Base 1

All arrays now start at zero, as arrays start in most programming languages. Again, this change is necessary for programming language interoperability, something that .NET tends to encourage (or at least, not discourage). In addition, you can’t specify a fixed array size in .NET as you could in 6.0:

Dim strValue(10) as String

Instead, you’ll use syntax similar to the following:

Dim iValue as Integer = new Integer(10) { }

This new approach allocates storage for 10 elements of the specified data type, but doesn’t fix the size of the array itself. The array can be changed through the use of the ReDim statement:

ReDim iValue(20)

To allow for maximum forward compatibility in your code, avoid the use of Option Base to reset the base value of an array. Not using this statement forces your arrays to begin with the zero boundary. The upgrade wizard will also convert arrays to being zero-based, if so instructed when the project is first opened.

Microsoft hasn’t restricted its Visual Basic changes to structural and data type changes. The use of built-in functions will also be impacted in .NET.

Built-in Functions and Namespaces

One brand new feature of Visual Basic .NET that I do want to mention in this article is the concept of namespaces. Namespaces are ways of exposing externally created components to an application, similar to importing a type library into C++ or attaching a reference to a type library in Visual Basic 6.0.

You can import a namespace into Visual Basic .NET code using the imports statement:

imports Microsoft.VisualBasic.ControlChars

With imports, you can reference members of the namespace without having to type the qualifying namespace name. Instead of:

dValue = Math.sqrt(number)

You can use:

dValue = sqrt(number)

The reason I’ve introduced the namespace concept into an article focusing on forward compatibility of VB ASP components is because many Visual Basic 6.0 built-in functions have now been defined within specific namespaces, such as the System.Math namespace, just shown.

One significant alteration to your code based on namespace use is with the String data type. Now, you can access Stringdirectly from within your code:

Dim strValue as String 

In Visual Basic.NET, String is actually defined as part of the System.String namespace. In fact, all supported data types are now a part of the System namespace, as is much of VB’s built-in functionality.

To ensure backwards compatibility, Microsoft has provided a specific namespace, Microsoft.VisualBasic.Compatibility.VB6 that you can import into your existing Visual Basic 6.0 components. This will allow applications to work within the Visual Basic .NET environment until you have time to make the actual modifications.

One final change in Visual Basic .NET I’ll look at is the impact on Properties within the new environment, and the use of Set and Let.

Property Changes

In Visual Basic 6.0, you use direct assignment when assigning scalar values but must use the Set keyword when assigning an object to a variable:

Set obj = SomeObject

In Visual Basic.NET, the infrastructure supporting properties has changed, and the Set and Get keywords are now disallowed in the programming language.

As a demonstration of this change, in the past when you’ve worked with ADO, you’ve assigned a Connection object to a variable using syntax similar to the following:

Set rs.ActiveConnection = conn

Chances are, you’ve forgotten to use the Set keyword, resulting in an assignment of the Connection object’s default property, the connection string, instead of the object itself:

rs.ActiveConnection = conn

With the above, the code will still work, a database connection is assigned to the ResultSet object, but what happens is that a second connection is created using the connection string rather than using the existing connection object.

Visual Basic .NET no longer supports the concept of default properties unless they take parameters. This is one of the design changes folks have been asking for, and one that was long overdue.

With this type of change, the accidental assignment of a default property, as described with the ADO Connection object, will no longer occur – a definite improvement within the language and the tool.

The .NET infrastructure also specifies a new approach for defining properties that provides the means to set and retrieve the property value. An example of a property within the PDC-based release of Visual Basic .NET is the following:

' Internal storage, always in degrees. 
Dim Degrees As Double = 0  
  Property Angle(ByVal Radians As Boolean) As Double 
  Get 
    Angle = Degrees 
    If Radians Then Angle = Angle * 3.1415926536 / 180 
  End Get 
  Set 
    ' The keyword Value stands for the value passed in. 
    Degrees = Value    
    If Radians Then Degrees = Degrees * 180 / 3.1415926536 
  End Set 
  End Property

By providing a means to define how a property is set and retrieved, the Property LetProperty Set, and Property Getstatements are no longer necessary and been removed from language support. Again, as with many of the other changes detailed in this article, the upgrade wizard will automatically correct the use of Set and Get and property management when your VB 6.0 component project is opened. Compiled components that have been wrapped via the use of TlbImp aren’t impacted by these code internal changes.

Summary

You’ve not yet had a chance to get a detailed look at all of the Visual Basic .NET changes – many are still being determined. However, you should have a better understanding of the types of changes headed your way, and how they will impact your existing VB components and component development.

Hopefully I’ve also shown that though the changes are significant, they aren’t overwhelming. In fact, there are many steps you can take now that will allow for a smooth migration of your VB component code to the new .NET architecture.

As I finished writing this article, Microsoft announced that it was restoring some of the Visual Basic 6.0 functionality it had originally pulled with the first beta release of VB.NET. This move was to ensure that VB.NET could support applications developed before the release of VB.NET.

However, if you’re creating new applications–or components–adjust your code to be in synch with VB.NET rather than depending on support for the older VB code base.

PerlScript: A hot ingredient for ASP

Originally published at Web Techniques

Microsoft’s Active Server Pages (ASP) technology has grown very popular as a Web server-side technique, combining HTML, embedded scripts, and external components to deliver dynamic content. One feature of ASP is that different languages can be used for its scripting portion, though the most widely used ASP scripting language is VBScript—a subset of Microsoft’s Visual Basic.

However, just because VBScript is the most popular language used by ASP developers doesn’t mean it’s the only one, or even the best one to use in a particular circumstance. For instance, Perl has long been synonymous with Web server development, beginning with the earliest uses of the language by CGI, and is still one of the most popular languages among Web developers. But there hasn’t been much discussion of using Perl with ASP.

If your organization has been working with Perl, and you’re interested in developing for the ASP environment, you don’t have to give up your favorite language (or your existing Perl code) to make the transition to the new technology—you can employ Perl for ASP scripting through the use of PerlScript.

A (Very) Brief Overview of ASP

Microsoft originally introduced ASP technology was with the company’s own Web server, Internet Information Server (IIS). However, ASP has now been ported to other Web servers through the use of software such as ChiliSoft’s platform-independent version of ASP. In addition, ASP was created originally to work in a Windows environment, but again thanks to ChiliSoft and other companies, ASP now runs in non-Windows environments such as UNIX and Linux.

Still, the most popular use of ASP is within the Windows environment, with pages hosted on IIS. This environment—specifically Windows NT/IIS 4.0—is the one I’ll discuss.

ASP pages have an .asp extension, and are a mix of HTML and embedded script. When a client requests the page, the embedded script is accessed and processed. The results generated by the script are embedded into the Web page, which is then returned to the client browser.

The Response object—along with the other built-in ASP objects Server, Session, Request, and Application—provides access to the ASP and application environment for the ASP developer. The Response object provides a way to send information back to the Web-page client; the Request object provides access to information sent from the client, such as form contents; the Application object contains information that persists for the lifetime of the ASP application; the Session object contains information that persists for the lifetime of the particular ASP user session; and the Server object, among other things, lets the ASP developer create external components.

ActivePerl and PerlScript

A company named ActiveState was formed in 1997 to provide Perl tools for developers for all platforms. Among some of ActiveState’s more popular products is ActivePerl, a port of Perl for the Win32 environment.

ActivePerl is a binary installation containing a set of core Perl modules, a Perl installer (Perl Package Manager), an IIS plug-in for use with Perl CGI applications, and PerlScript. What is PerlScript? PerlScript is Perl, but within an ASP environment—it’s a Perl implementation of the Microsoft Scripting Engine environment, which allows Perl within ASP pages.

ActivePerl can be downloaded for free from ActiveState’s Web site (see the ” Online Resources“). To try it for yourself, access the ActiveState Web site and find the ActivePerl product page.

The installation process requires very little user input. You’ll need to specify where you want to put the files, and be sure to select the option to install PerlScript.

Using PerlScript Within ASP Pages

By default in IIS, all ASP scripting is VBScript, but you can change this using one of three different techniques. First, you can change the default scripting language for the entire ASP application using the IIS Management Console, and accessing the Properties dialog box for the Virtual Directory or Site where the ASP application resides. Click on the Home Directory (or Virtual Directory) tab, and then click on the Configuration button on the page. From the dialog box that opens, select the App Options page. Change the default scripting language from “VBScript” to “PerlScript.”

A second technique is to specify the scripting language in the scripting block itself. With this technique you could actually choose more than one scripting language in the page:

<SCRIPT language=”PerlScript” RUNAT=”Server”> . . . </SCRIPT>

A third technique is to specify the scripting language directly at the beginning of the Web page. This is the approach we’ll use for examples. Add the following line as the first line of the ASP page:

<%@ LANGUAGE = PerlScript %>

All scripting blocks contained in the page are now handled as PerlScript.

Accessing Built-In ASP Objects From PerlScript

To assist the developer, PerlScript provides access to several objects available only within the ASP environment. As mentioned earlier, these are the Application, Session, Response, Request, and Server objects.

The Application object is created when an ASP application first starts, and lasts for the lifetime of the application. The object has two COM collections, both of which can be accessed from script: the StaticObjects collection, with values set using the <OBJECT> tag within an application file called global.asa; and the Contents collection, which contains values set and retrieved at runtime. A COM collection is a group of similar objects treated as a whole, with associated features that let the developer access individual elements directly, or iterate through the entire collection.

In addition to the two collections, the Application object also has two methods, Lock and UnLock, which are used to protect the object against attempts by more than one application user at a time to change its values.

We’ll take a closer look at using the Lock and UnLock methods by setting a value in the Application’s collection in one ASP page, and then retrieving that same value from another page.

First, Listing 1 contains a page that sets a new value to Application Contents by first locking down the object, setting the value, and then unlocking the object. Notice that you don’t have to create the Application object—it’s created for you, and exists in the main namespace of the PerlScript within the ASP page (the same holds true for all of the ASP objects).

If you’ve worked with VBScript you’ve probably noticed that you have to use a different technique to set the value in the Contents collection with PerlScript. VBScript allows direct setting of collection values and properties using a shorthand technique, similar to the following:

Application.Contents("test") + val

PerlScript, on the other hand, doesn’t support this shorthand technique. Instead, you have to use the SetProperty method to set the Contents item:

$Application->Contents->SetProperty('Item', 'test', $val);

Additionally, you have to use SetProperty to set ASP built-in object properties with PerlScript, or you can use the Perl hash dereference to set (and access) the value:

my $lcid = $Session->{codepage};

Listing 2 contains another ASP page that accesses the variable set in Listing 1, prints out the value, increments it, and resets it back to the Application object. It then accesses this value and prints it out one more time. Accessing this page from any number of browsers, and from any number of separate client sessions, increments the same Application item because all of the sessions that access the ASP application share the same Application object.

In addition to the Application object, there is also a Session object, which persists for the lifetime of a specific user session.

This object is created when the ASP application is accessed for the first time by a specific user, and lasts until the session is terminated or times out, or the user disconnects in some way from the application. It, too, has a StaticObjects and a Contents collection, but unlike the Application object, you don’t lock or unlock the Session object when setting a value in Contents. But you do access the Contents collection in the exact same manner, both when setting a value:$Session->Contents->SetProperty('Item', 'test', 0);

as well as when retrieving the value:

my $val = $Session->Contents('test');

Additional Choices

There are also other properties and methods available with Session, including the Timeout property, used to set the session’s timeout value, and the Abandon method, used to abandon the current session. Each session is given a unique identifier, SessionID, and this value can be accessed in a script. But use caution when accessing this value if you hope to identify a unique person—the value is only unique and meaningful for a specific session.

In support of internationalization, there are Session properties that control the character set used with the page, CodePage, and to specify the Locale Identifier, LCID. The Locale Identifier is an international abbreviation for system-defined locales, and controls such things as currency display (for instance, dollars versus pounds).

Listing 3 shows an ASP page that sets the Timeout property for the Session object, and accesses and prints out both the CodePage and the LCID values. Retrieving this ASP page in my own environment and with my own test setup, the value printed out for CodePage is 1252—the character mapping used with American English and many European languages. The value printed out for LCID is 2048—the identifier for the standard U.S. locale.

The Application and Session examples used a third ASP built-in object, the Response object. This object is responsible for all communication from the server application back to the client. This includes setting Web cookies on the client (with the Cookies collection), as well as controlling whether page contents are buffered before sending back to the client, or sent as they’re generated, through the use of the Buffer property:

$Response->{Buffer} = 1;

You can use the Buffer property in conjunction with the End, Flush, and Clear methods to control what is returned to the client. By setting Buffer to true (Perl value of 1), no page contents are returned until the ASP page is finished. If an error occurs in the page, calling Clear erases all buffered contents up to the point where the method was called. Calling the End method terminates the script processing at that point and returns the buffered content; calling the Flush method flushes (outputs) the buffered contents, and ends buffering.

Listing 4 shows an ASP page with buffering enabled. In the code, the Clear method is called just after the first Response Write method, but before the second. The page that’s returned will then show only the results of the second Write method call.

The Buffer property must be set before any HTML is returned to the client, and this restriction also applies to several other Response properties, such as the Charset property (alters the character setting within the page header); the ContentType property (alters the MIME type of the content being returned, such as “text/html”); the AddHeader method, which lets you specify any HTTP header value; and the Status property, which can be used to set the HTTP status to values such as “403 Forbidden” or “404 File not found”. For example:

$Response->{Status} = "403 Forbidden";

If buffering is enabled for a Web page, then properties such as Charset and ContentType, as well as the AddHeader method can be used anywhere within the ASP page.

You can redirect the Web page using the Redirect method call to specify a new URL. As with the other properties and methods just mentioned, Redirect must also occur before any HTML content:

$Response->Redirect("http://www.somesite.com");

In addition to manipulating HTTP headers, the Response object also generates output to the page using the Write method, as demonstrated in the previous examples. You can also return binary output to the client using BinaryWrite. You can override whether an ASP page is cached with proxy servers through the use of the CacheControl property, as well as set cache expiration for the page by setting the Expires or the ExpiresAbsolute properties:

$Response->{Expires} = 20 # expires in 20 minutes

You can test to see if the client is still connected to the setting with the IsClientConnected property. Communication doesn’t just flow just from the server to the client. The Request object handles all client-based communication in either direction. This object, as with Response, has several different methods, properties, and collections. The collections interest us the most.

You can read Web cookies using the Request Cookies collection. And it’s possible to set and get information about client certificates using the ClientCertificate collection.

You can also process information that’s been sent from a client page using an HTML form, or appended as a query string to the Web page’s URL:

<a href=”http://www.newarchitectmag.com/documents/s=5106/new1013637317/somepage.asp?test=one&test2;=two”>Test</a>

The two collections that hold information passed to the ASP page from a form or a query string are: the QueryString collection, and the Form collection. Use QueryString when data is passed via the GET method, and use Form when data is passed via the POST method.

Regardless of which technique you use to send the name/value pairs, accessing the information is similar. Listing 5 shows an ASP page that processes an HTML form that has been POSTed:

The example ASP page pulls out the values of three text fields in the form: lastname, firstname, and email. It then uses these to generate a greeting page, including adding a hypertext link for the email address. If the form had been sent using the GET method (where the form element name/value pairs get concatenated onto the form processing page’s URL), then the page contents would be accessed from QueryString:

my $firstname = $Request->QueryString('firstname')->item;In addition to the Form and QueryString collections, the Request object also has a ServerVariables collection, containing information about the server and client environment. The ServerVariables collection is comparable to accessing %ENV in CGI.

You can access individual elements in the Server Variables collection by specifying the exact variable name when accessing the collection:

my $val = $Request->ServerVariables('PATH_TRANSLATED')->item;Or you can iterate through the entire collection. To do this, you can use the Win32::OLE::Enum Perl module to help you with your efforts. The Enum class is one of the many modules installed with ActivePerl, and provides an enumerator created specifically to iterate through COM collections such as ServerVariables.

Listing 6 shows an ASP page that uses the Enum class to pull the elements of the ServerVariables collection into a Perl list. You can then use the Perl foreach statement to iterate through each ServerVariables element, printing out the element name and its associated value.

If an HTML form contains a File input element—used to upload a file with the form—you can use the Request BinaryRead method to process the form’s contents. The TotalBytes property provides information about the total number of bytes being uploaded.

Break Out with COM

All of the examples up to this point have used objects that are built in to the ASP environment. You can also create COM-based components within your ASP pages using the built-in Server object. The Server object has one property, ScriptTimeout, which can be used to determine how long a script will process—a handy property if you want to make sure scripting processes don’t take more than a certain length of time.

The Server object also has a couple of methods that can be used to provide encoding, such as HTML encoding, where all HTML-relevant characters (like the angle bracket) get converted to their display equivalents. MapPath maps server paths relative to their machine locations. URLEncode maps URL-specific characters into values that are interpreted literally rather than procedurally:my $strng = $Server->URLEncode("Hello World");The result of this method call is a string that looks like:

Hello+World%21Although these methods and the one property are handy, Server is known generally as the object used to instantiate external ASP components, through the use of the CreateObject method. This method takes as its parameter a valid PROGID for the ASP component. A PROGID is a way of identifying a specific COM object, using a combination of object.component (sometimes with an associated version number):

simpleobj.smplcmpntAs an example, I created a new Visual Basic ActiveX DLL, named the project simpleobj, and the associated class smplcmpnt. The component has one method, simpleTest, which takes two parameters and creates a return value from them, based on the data type of the second parameter. This component method, shown in Example 1, has a first parameter defined as a Visual Basic Long value (equivalent to a Perl integer), and a second parameter of type Variant, which means the parameter could be of any valid COM data type—Visual Basic functions are used to determine the data type of the value.

A new page uses the Server CreateObject method to create an instance of this ASP component, and tests are made of the component method. As shown in Listing 7, the first test passes two integers to the external VB component method. The component tests the second parameter as a Long value, adds the two parameters, and returns the sum.

The next test passes a string as the second parameter. The component tests this value, finds it is not a number, and concatenates the value onto the returned string.

The script for the final test creates a date variable using the Win32::OLE::Variant Perl module, included with ActivePerl. The standard localtime Perl method is used to create the actual date value, but if this value isn’t “wrapped” using the Variant module, the Visual Basic ASP component will receive the variable as a string parameter rather than as an actual date—PerlScript dates are treated as strings by ASP components.When the Visual Basic component receives the date as the second parameter, the component finds that it is not a number, and concatenates the value onto the string returned from the function. When displayed, the returned string looks similar to the following:

3/15/1905 100I could have passed the date value directly instead of using Variant, but as I mentioned, the COM-based VB component sees the Perl date as a string rather than as a true date type. The Variant Perl module provides techniques to ensure that the data types we create in PerlScript are processed in specific ways in ASP components.

Summary

Perl is a mature language that has been used for many years for Web development. As such, there is both expertise with, and a preference for, using this language for future development with the newer Web development techniques such as ASP.

ActivePerl and PerlScript are the key tools for using Perl within the ASP environment. Perl can be used for ASP scripting through PerlScript, but the Perl developer also has full access to the objects necessary to work within the ASP environment: namely the ASP built-in objects such as the Response and Request objects.

Additional modules to assist Perl developers—such as Win32::OLE::Enum and Win32::OLE::Variant—are included with the ActivePerl installation, and help make PerlScript as fully functional within the ASP scripting environment as VBScript.

Best of all, with ActivePerl and PerlScript you can develop within an ASP environment and still have access to all that made Perl popular in the first place: pattern matching and regular expressions, the Perl built-in functions, and a vast library of free or low-cost Perl modules to use in your code. Interest in ASP is growing, and with PerlScript you can work with this newer Web technology and still program in your favorite programming language.

Creating a Shopping Cart ASP Component

Originally published in ASP Today, October 20, 1999

As soon as HTML forms were added to the HTML specification, and CGI use extended to server-side applications, folks immediately thought of using the Web for online stores – the concept of the shopping cart was born. If you’ve ever done any online shopping, you’ve used a shopping cart.

A shopping cart is basically a small application that maintains a list of the Web shopper’s selections in such a way that they can be viewed and modified at any time. By itself, the cart is a fairly simple application, but folks end up rolling an inventory control system, an order processing system, a customer service system and the overall Web site maintenance into one application with the misnomer “shopping cart”. What should be a small, compact system sprawls into something large and difficult to manage.

The way a cart keeps tracks of items and persists the list of those items, tends to differ from implementation to implementation: they might be tracked for a single session, or persist from session to session. The cart itself can be created on a specific computer or be accessible from many computers. Although a shopping cart application can interface with other applications, like an order system, an inventory system, or general Web maintenance, it does not implement this functionality itself:

This article will look at the creation of a simple ASP-based shopping cart application that uses a Visual Basic shopping cart component – all of which can be found in the download at the end of the article, with a text file telling you what you’ll need and how to use it.

Shopping Cart Implementation Requirements

First and foremost, a shopping cart has to persist from Web page to Web page, so some sort of technique needs to be used to associate an identifier with a shopping cart and then persist that identifier between Web pages. The items contained in the cart do not need to be accessible from all pages, but the cart identifier does. Because of this essential functionality, shopping carts are usually dependent on Web client Cookies to maintain the link between the cart and the shopper.

There are other techniques that can be used to persist information about the cart between pages. Some developers use hidden form fields (containing the shopping cart identifier or possibly a string of the cart’s items), or add shopping cart information to the end of the URL of the new page that is being accessed. Both of these techniques make the cart information available in the new page. To use these techniques in an ASP shopping cart, you could get the hidden form fields from the Forms collection of the Request built-in ASP object; or the appended URL information from the QueryString collection that is, again, part of the Request object.

If a store chooses to support a shopping cart for a session only (carrying information from the user’s initial store access until they log out, close their browser or disconnect from the Internet), then either hidden form fields or query string method will work fine. However, if cart needs to persist beyond the session, then Cookies are the way forward. Using Cookies, the Web developer can persist the shopping cart for the session or for a specified period of time.

Using Cookies alone, the shopping cart and its contents are maintained solely on the client, so the cart’s contents can be accessed quickly. There is, unfortunately, a major limitation with this approach – Cookies can only hold so many items, usually not many more than 50 – 75. Should the cart need to hold more than this, then Cookie technology on it’s own simply isn’t a viable approach, because the Cookie string can get too large. Even a couple of items can create a large Cookie string.

There is another problem with Cookie technology: the cart can’t follow the shopper. So if a shopper starts a cart on their laptop then they have to keep it there: they can’t access the same cart from their desktop computer at home, or a computer at work, because Cookies don’t travel. The shopper could export the cookies to all of their computers if the browser they’re using supports this technology, but most folks don’t consider exporting cookies and many aren’t even aware of this capability.

The solution, then is to bring the shopping cart to the computer and have it create whatever Cookies it needs to be supported in the new environment.

The Ideal Shopping Cart

The ideal Shopping cart that we could implement using ASP and Visual Basic, for the purposes of this article, will allow:

  • An item to be added to a shopping cart at the touch of a button
  • Shopping cart items to persist for more than one shopping session
  • Some indication that there are items in the shopping cart to be displayed, at least on the site’s home page
  • The Web shopper to view the shopping cart contents at any time, and the contents to be displayed whenever an object is added to the cart
  • The store to provide a means to modify the shopping cart items: to remove an item / all items
  • A running total to be maintained each time the shopping cart contents are reviewed
  • The shopping cart to follow the client
  • Support for an indefinite number of items

In order to create the shopping cart, we must first create the cart database support and the cart Visual Basic component project.

Setting up the Cart’s Environment

Instead of implementing all the aspects of the shopping cart within ASP script, we’ll implement the business logic within a Visual Basic component, use stored procedures for database access, and integrate the shopping cart into the ASP infrastructure within the script. Using this separation of functionality will isolate the data access and database structures from the business logic, and isolate the business logic from the implementation environment.

The tables to support the shopping cart are simplified to include only that information necessary to implement the cart: CART , CARTITEM , CUSTOMER , and CUSTOMERCART , and WIDGET . The WIDGET table represents the product table for this example. The table CARTITEM is dependent on both CART and WIDGET , and the CUSTOMERCART is dependent on both CUSTOMER and CART , so foreign key relationships exist between these tables.

The SQL to create the tables within a SQL Server 7.0 database, and the associated indexes and foreign keys are included with the download example code attached to this article.

In addition to the tables, several stored procedures are used to manage data access. Each of these procedures will be described as they are accessed by specific business routines in the sections ahead; but before we can add the methods to implement the business logic, we’ll need to create the Visual Basic project.

Creating the Cart Project

The shopping cart component is created as a new, ActiveX DLL Visual Basic project named, appropriately enough, shopcart . References to the Microsoft ActiveX Data Objects (ADO 2.1 for this example) Library and the Microsoft Transaction Server (MTS) Type Library are added to the project. We’re adding in support for MTS so the component will be able to access the ObjectControl interface, and we’re adding in support for ObjectControl in order to enable just-in-time activation for the shopping cart component.

Some basic component functionality is added, including the use of Option Explicit at the top of the class file, and the ObjectControl implementation. The only functionality added to the ObjectControl methods (Activate, Deactivate, CanBePooled) at this time is to define a member that holds the Database connection string.

Option Explicit

' The connection string is available to all shopping cart methods
Private m_connString As String

'Implementation of ObjectControl interface
Implements ObjectControl

' ObjectControl Methods
Private Sub ObjectControl_Activate()
    m_connString = "driver=
{SQLServer};server=FLAME;database=writing;uid=sa;pwd="

End Sub

Private Function ObjectControl_CanBePooled() As Boolean
    ObjectControl_CanBePooled = False
End Function

Private Sub ObjectControl_Deactivate()
 ' no activity
End Sub

Because there is a lot to get through in this article, I’m not going to go into the ObjectControls method (these areas will be covered in another article, coming soon – Ed ). The next thing to add is the functionality specific to our implementation of the shopping cart, starting with the methods to create the shopping cart and to add an item to the cart.

Adding Methods to Create a Cart and Add Items

The first requirement of the shopping cart is that users can add items to it, and implicitly, the ability to create a cart itself. We’ll implement both of these requirements as methods.

A cart can be created either when a shopper first accesses a site, or when the shopper makes an initial move to adding an item to the cart. The cart I’m going to build here will take the second approach, will only be created if none already exists, and will be created through a method called in the page that displays the shopping cart contents.

You can add a new cart through a stored procedure called SP_NEWCART , which adds a new record to the CART table, and returns a unique cart identifier:

CREATE PROCEDURE [SP_NEWCART] 
AS
BEGIN
insert into cart (date_created)
values (getdate())
select max(cart_id) from cart
END

To access this stored procedure, a function named createCart , having no parameters and returning a LONG value, is added to the cart component. The returned value is the new shopping cart identifier returned from SP_NEWCART .

Within createCart , new Command and Recordset objects are created and a connection string is added to the ActiveConnection property of the Command object. In addition, the Command CommandType is set to adCmdStoredProc and the stored procedure name is assigned to the Command object’s CommandText property.

' createCart
' Generate shopping cart ID
' create cart without customer association
'
Function createCart() As Long

  Dim comm As New Command
  Dim rs As New Recordset

  ' open connection, attach to Command object
  comm.ActiveConnection = m_connString
  
  ' set Command object properties
  comm.CommandText = "SP_NEWCART"
  comm.CommandType = adCmdStoredProc
 
  ' execute command and get output value (cartid)
  Set rs = comm.Execute
  
  ' get cartid
  rs.MoveFirst
  createCart = rs(0)

  rs.Close

End Function

When the Command object is executed, a record is returned and assigned to the Recordset object. Only one value is returned with the record, the cart identifier, which is then assigned to the function name and returned to ASP application.

To integrate this new component method and associated functionality into the ASP shopping cart application as a whole, the Cookies collection of the ASP built-in Request object is accessed, and the contents examined for an already identified cart. If one is found then it is used as the cart identifier for displaying cart contents. Otherwise, an instance of the shopping cart component is created and the createCart function is called. The newly returned cart identifier is then assigned to the Cookies collection of the Response built-in ASP object, and the cart identifier is created as a client-side cookie. Doing this persists the cart identifier between pages of the shopping cart application, and even beyond the current shopping session. In the example, the Cookie persists until the date set in the Expires property of the Cookie, which is December 31, 2001 in our code.

cartid = cart.createCart()
Response.Cookies("cartid") = cartid
Response.Cookies("cartid").Expires = "December 31, 2001"

We’ve implemented the functionality to add a new cart, but of course it isn’t very useful unless we can add items to it:

First, a stored procedure is created, SP_ADDITEM , to handle the addition of a new cart item. Within this procedure, a check is made of the table CARTITEM to see if a record already exists for the specific cart and product item. If found, the quantity passed to the stored procedure is added to the quantity for the cart item. If a record is not found, a new entry to CARTITEM is made for the specific cart and product.

CREATE PROCEDURE [SP_ADDITEM]
(@cartid int, @itemid int, @qty int)
 AS
BEGIN
IF (select count(*) from cartitem where cart_id=@cartid and 
      widget_id = @itemid) > 0 
   update cartitem
     set quantity = quantity + @qty where 
      cart_id = @cartid and widget_id = @itemid
ELSE
    insert into cartitem values (@cartid, @itemid, @qty, getdate())
END

The stored procedure SP_ADDITEM is called from within a new method, addItem , and added to the shopping cart component. In this method, the ADO Connection object is used both to connect to the database and invoke the stored procedure.

' addItem
' Adds item to shopping cart
' If more than one item, update quantity in SP
'
Sub addItem(ByVal lCartID As Long, ByVal lItemID As Long)

  Dim conn As New Connection
  
  ' connect to database
  conn.ConnectionString = m_connString
  conn.Open
 
  ' build command string
  Dim strComm As String
  strComm = "SP_ADDITEM " & CStr(lCartID) & "," & CStr(lItemID) & ",1"
                
  ' execute command
  conn.Execute strComm

  conn.Close

End Sub

The cart identifier discussed earlier and the product identifier are passed as parameters to addItem . We already have the cart identifier, and so the product identifier is passed to the shopping cart page from a form on a product page. The value is accessed from the Form collection of the built-in ASP Request object.

Dim itemid
itemid = Request.Form("itemid")

If itemid <> "" Then 
   cart.addItem cartid, itemid
End If

So, at this point we’ve created a cart and added an item to it. The next logical step to take in developing the shopping cart component and application is to provide a technique for displaying the cart contents:

Displaying the Cart Contents

The shopping cart display is the most visual aspect of a shopping cart application, and it is also one of the easiest to implement. Basically, the shopping cart items are accessed and displayed, as rows, usually within an HTML table.

A new stored procedure is created, SP_GETITEMS , which gets information from the CARTITEM and the WIDGET tables. The items that the cart contains are located in CARTITEM , but the information about the item, such as product name, price, and quantity per unit are found inWIDGET , hence the join between both tables. Additionally, a total price is calculated from the quantity of items ordered and the price per item, and this total is added as a “column” to the record being returned.

CREATE PROCEDURE [SP_GETITEMS]
(@cartid int) AS
select widget.widget_id, 
          short_name, 
          qty_unit,
          price,
          quantity,
          price * quantity total
    from cartitem,widget where cart_id = @cartid and 
    widget.widget_id = cartitem.widget_id

A new function is created, getItems , which calls SP_GETITEMS and returns the resulting recordset as a disconnected recordset . By returning the entire recordset to the ASP page, we can use built-in Recordset functionality to access and display the returned records and the individual fields. By using a disconnected recordset, the database connection is released before the recordset is returned to the ASP page, and valuable database resources aren’t being tied up unnecessarily.

' getItems
' return list of items, short decriptions, quantity
' as disconnected recordset
'
Function getItems(ByVal lCartID As Long) As ADODB.Recordset

  Dim conn As New Connection
  Dim rs As New Recordset
  conn.ConnectionString = m_connString

  ' connect to database
  conn.Open
  Set rs.ActiveConnection = conn
  
  ' set and open recordset
  rs.CursorLocation = adUseClient
  rs.Source = "SP_GETITEMS " & CStr(lCartID)
  rs.Open

  ' disconnect recordset
  Set rs.ActiveConnection = Nothing
  conn.Close

  Set getItems = rs.Clone

  rs.Close
End Function

An HTML table is created within the body of the ASP shopping cart page, and table headers are used to provide column labels for the individual recordset fields. Because the shopping cart can be updated — new quantities can be added for an item or an item can be removed – the HTML table displaying the cart items is contained within an HTML form, so the changes can be submitted back to the shopping cart application.

Following the HTML table and form definitions, ASP script is used to access the recordset with the cart items and output the recordset rows as table rows (records) and cells (columns).

Dim total
Do While rs.EOF = False
     total = total + rs(5)
     Response.Write("<TR>")
     Response.Write("<TD align='middle'><input type='hidden' name='itemid' value='" & rs(0) & "' size=10>")
     Response.Write("<strong>" & rs(0) & "</strong></TD>")
     Response.Write("<TD align='left'><strong>" & rs(1) & "</strong></TD>")
     Response.Write("<TD align='middle'><strong>" & rs(2) & "</strong></TD>")
     Response.Write("<TD align='right'><strong>" & FormatNumber(rs(3),2) & "</strong></TD>")
     Response.Write("<TD bgcolor='white' align='middle'><input type='text' name='quantity' value='" & rs(4) & "' size=10></TD>")
     Response.Write("<TD align='right' ><strong>" & FormatNumber(rs(5),2) & "</strong></TD>")
     Response.Write("</TR>")
     rs.MoveNext
Loop  
Response.Write("<TR><TD align=right colspan=6><strong>Cart Subtotal is: $" & FormatNumber(total,2) & "</strong></td></tr>")
 

Notice in the ASP script that a hidden form field holds the product item identifier for each row, and another form input element holds the quantity of items added to the cart for the item. The hidden field is used to tie a product identifier to quantity, and the quantity field is a text input element, giving the Web shopper to ability to modify the quantity of a specific item in the cart.

Following the ASP script, traditional HTML is again used to provide handling of form submission, including options to submit the shopping cart to the order processing system, return to the main store page, continue shopping, and to update the shopping cart to process a quantity change. Updating quantities is discussed in the next section.

Updating Shopping Cart Contents

Imagine for a moment going into a grocery store and adding several items to your shopping cart. Now imagine not being able to remove an item from the cart once the item is placed there, or being unable to change the quantity of a specific item in the cart. If you couldn’t modify the cart contents at a “real” store you probably wouldn’t return to the store and the same applies to the shopping cart implemented at a virtual store. Shopping carts must provide the capability for Web shoppers to modify their cart contents after the contents have been added.

Modifying cart items includes being able to change the quantity of an item in the cart and to remove an item from the cart altogether — two different functions that really only requires one stored procedure, SP_UPDATEQTY . The quantity being passed is checked within the stored procedure : if the value is zero (0), the cart item is deleted from CARTITEM ; otherwise the value is updated. In addition, the stored procedure checks to see if a row exists for the cart and item in CARTITEM . If it does, the value is updated; otherwise the stored procedure SP_ADDITEM is called to create a new cart item with the new quantity. Another approach to removing the item could be to add a button to delete the item from the cart. This could set the quantity to zero, or even call submit the cart for update immediately – either approach works.

CREATE PROCEDURE [SP_UPDATEQTY]
(@cartid int, @itemid int, @qty int)
 AS
BEGIN
   IF @qty = 0
      delete from cartitem where cart_id = @cartid and widget_id = @itemid
   ELSE IF (select count(*) from cartitem where cart_id = @cartid and 
         widget_id = @itemid) > 0
      update cartitem
      set quantity =  @qty where 
          cart_id = @cartid and widget_id = @itemid
  ELSE
      exec sp_additem @cartid, @itemid, @qty
END

The SP_UPDATEQTY stored procedure is called from a new method added to shopping cart component and called updateItemQty . This method has three parameters, the cart identifier, the product identifier and the quantity. It checks to make sure the quantity isn’t negative, and then builds a call to SP_UPDATEQTY .

' updateItemQty
' Update quantity of item
' return new count of items
'
Sub updateItemQty(ByVal lCartID As Long, _
                        ByVal lItemID As Long, _
                        ByVal lQuantity As Long)
                        
  Dim conn As New Connection
  
  ' quantity cannot be less than zero
  If lQuantity < 0 Then
      Err.Raise 5 ' invalid argument error
  End If
       
  ' connect to database
  conn.ConnectionString = m_connString
  conn.Open
 
  ' build command string
  Dim strComm As String
  strComm = "SP_UPDATEQTY " & CStr(lCartID) & "," & CStr(lItemID) & _
                "," & CStr(lQuantity)
                
  ' execute command
  conn.Execute strComm
  conn.Close

End Sub

The updates to the quantities occur in the same shopping cart display page that receives new product items, so way of determining whether an item is being added or the quantity is being updated needs to be added in. For our example, a hidden field is added to the update quantity form on the shopping cart display page, and to the product form on the product display page. The hidden field has a name of startpos , and the value attached to this field determines what action is taken when the shopping cart page is accessed. If a value of update is accessed, then an update is to be made.

<input type="hidden" name="startpos" value="update">

If a value of additem is found, then the add item functionality is used.

<input type="hidden" name="startpos" value="additem">

With the addition of the new hidden fields, and the update quantity component method, the ASP script in the shopping cart page is amended to allow for adding new items and updated quantities.

Dim action
 action = Request.Form("startpos")
 If action = "additem" Then
    Dim itemid
    itemid = Request.Form("itemid")

    If itemid <> "" Then 
      cart.addItem cartid, itemid
    End If

  ElseIf action = "update" Then
    For i = 1 to Request.Form("quantity").Count
       cart.updateItemQty cartid, Request.Form("itemid")(i), _
				 Request.Form("quantity")(i)
    Next
  End If

We now need to add the functionality that associates the cart with a specific customer, so that the customer can access this cart from any computer, and that empties the cart if the customer decides not to place an order. These are detailed next.

Associating Cart to Customer and Emptying Cart

At this time we have all the functionality necessary to create a cart, add items to the cart, persist the cart beyond a specific session and update the cart contents. However, to make the cart callable, which means a client can access it from any computer, we need to associate the cart to a customer.

A new stored procedure, named SP_CUSTOMERCART is created, which simply updates the cart’s customer identifier field with a specific customer identifier. How the customer identifier is accessed and the login procedure for the customer is outside the scope of the shopping cart application.

CREATE PROCEDURE [SP_CUSTOMERCART]
(@cartid int, @custid int)
 AS
BEGIN
  update cart set customer_id = @custid where 
  cart_id = @cartid
END

The stored procedure is accessed from a new shopping cart component method, addCartToCust, which does a couple of tasks. First, the method checks to see if the customer already has a cart and if so, the contents of the new cart are transferred to the older cart and the new cart is destroyed; otherwise the customer is assigned to the new cart.

' addCartToCust
' associate customer to cart
'  -- shipping cost and tax comes from customer state
'     these values cannot be calculated without customer
'
Function addCartToCust(ByVal lCartID As Long, ByVal lCustomerID As Long) As Long

  Dim lcart As Long
  
  ' check for existing customer cart
  ' if found, merge contents
  lcart = getCartID(lCustomerID)
  
  If lcart > 0 Then   ' existing customer cart found
     Dim rs As New Recordset
     Set rs = getItems(lcart)
     Dim i As Integer
     
     ' transfer new quantities to existing cart items
     For i = 1 To rs.RecordCount
        updateItemQty lcart, rs(0), rs(4)
     Next i
     
     ' destroy 'new' cart, use existing
     If lcart <> lCartID Then
        clearCart (lCartID)
     End If
  ElseIf lcart = 0 Then   ' no existing cart
    lcart = lCartID
    ' connect to data store
    Dim conn As New Connection
    conn.ConnectionString = m_connString
    conn.Open
    
    ' build command string and execute command
    Dim strCmd As String
    strCmd = "SP_CUSTOMERCART " & CStr(lCartID) & "," & CStr(lCustomerID)
    conn.Execute strCmd
    conn.Close
  End If
  
  ' return cart id
  addCartToCust = lcart

End Function

The addCartToCust method itself calls other component methods. The updateItemQty, discussed earlier, is used to transfer the contents of the new cart to the existing cart. In addition, a couple of new methods are created and used: one, getCartID, is used to return a cart identifier given a customer identifier; the other, clearCart, will remove the cart and its contents.

The getCartID uses a stored procedure called SP_GETCARTID to get any cart identifier for a given customer.

CREATE PROCEDURE [SP_GETCARTID]
(@customerid int)
 AS
BEGIN
select cart_id from cart where customer_id = @customerid
END

The method is fairly simple, basically little more than a call to the stored procedure, and validation checks to make sure a value of zero(0) is returned if no cart identifier is found.

'
' getCustomerID
' get customer id given cart id
'
Function getCustomerID(ByVal lCartID As Long) As Long

  Dim conn As New Connection
  Dim rs As New Recordset
  conn.ConnectionString = m_connString

  ' connect to database
  conn.Open
  Set rs.ActiveConnection = conn
  
  ' set and open recordset
  rs.CursorLocation = adUseClient
  rs.Source = "SP_GETCUSTOMERID " & CStr(lCartID)
  rs.Open
  
  ' get cartid
  If rs.RecordCount > 0 Then
    rs.MoveFirst
    If IsNull(rs(0)) Then
       getCustomerID = 0
    Else
       getCustomerID = rs(0)
    End If
  Else
    getCustomerID = 0
  End If
  
  rs.Close
  conn.Close

End Function

The clearCart method calls a stored procedure called SP_CLEARCART that deletes the cart items associated with a cart first, and then deletes the cart.

CREATE PROCEDURE [SP_CLEARCART] 
(@cartid int)
AS
BEGIN
   delete from cartitem where cart_id = @cartid
   delete from cart where cart_id = @cartid

END

The clearCart method itself is literally nothing more than a ASP component wrapper for the stored procedure call.

' clearCart
' Clears cart, removes all items
' disassociates customer from cart
'
Sub clearCart(ByVal lCartID As Long)

  Dim conn As New Connection
  
  ' connect to database
  conn.ConnectionString = m_connString
  conn.Open
  
  ' call stored procedure
  conn.Execute ("SP_CLEARCART " & CStr(lCartID))
  conn.Close

End Sub

Now, the ASP script to add a customer to a cart can be run when the person first logs into the store, or when an order is made – this is up to the individual Web store developer. In the example we’re working with, the cart is added to the customer the first time an item is added to the cart. The script itself is fairly simple.

' create the component instance
Dim cart
Set cart = Server.CreateObject("shopcart.cart1")

Dim cartid
cartid = Request.Cookies("cartid")
If cartid = "" Then
    cartid 0
End If

Dim customerid
customerid = Request.Cookies("customerid")
Dim action

If customerid <> "" Then
	cartid = cart.getCartID(customerID)
End If

action = Request.Form("startpos")
If cartid = 0 AND action <> "" Then
    cartid = cart.createCart()
    Response.Cookies("cartid") = cartid
    Response.Cookies("cartid").Expires = "December 31, 2001"

    If customerid <> "" Then
	cart.addCartToCust cartid, customerid
    End If
End If

In the code, the “action” variable is accessed from the Form collection. If an empty string is returned then we know that the shopping cart is not being called as a result of a quantity update, nor is it being called as a result of adding a new item. The shopping cart page is being called purely to display the cart, as a request from the Web shopper.

At this time, we have a shopping cart component, stored procedures, and supporting ASP pages to create a cart, add items to the cart, modify items in the cart, associate the cart with a customer, and destroy the cart. Are we finished? Not quite yet, we have one more requirement left to implement: we need to show how many items a person has in a cart from the store’s home page.

Displaying Summary Information about the Cart

Displaying information about a cart on the home page of a store is relatively simple. A stored procedure is created to return summary information about the cart such as the quantity of items ordered and the total cost (without shipping and tax). This stored procedure is named SP_GETITEMTOTALS .

CREATE PROCEDURE [SP_GETITEMTOTALS]
(@cartid int)
 AS
BEGIN
   select sum(quantity) items, sum(price * quantity) total from 
   cartitem, widget where 
   cart_id = @cartid and 
   widget.widget_id = cartitem.widget_id
END

This stored procedure is then called from within a method, getItemTotals that has a cart identifier as a parameter and returns a disconnected recordset containing the cart information to the ASP page.

'
' getItemTotals
' Returns count of items currently in basket
'
Function getItemTotals(ByVal lCartID As Long) As ADODB.Recordset

  Dim conn As New Connection
  Dim rs As New Recordset
  
  ' connect to database
  conn.ConnectionString = m_connString
  
  conn.Open
  Set rs.ActiveConnection = conn
  
  ' set and open recordset
  rs.CursorLocation = adUseClient
  rs.Source = "SP_GETITEMTOTALS " & CStr(lCartID)
  rs.Open

  ' disconnect recordset
  Set rs.ActiveConnection = Nothing
  conn.Close

  Set getItemTotals = rs.Clone
  
  rs.Close
End Function

In the ASP page, the disconnected recordset is then used to access the number of items in the cart and the total, which are then displayed to the page.

Dim cart
Set cart = Server.CreateObject("shopcart.cart1")

Dim cartid
cartid = Request.Cookies("cartid")
Dim customerid
customerid = Request.Cookies("customerid")
Dim rs

If customerid <> "" Then   
  Dim firstname
  Dim lastname
  Set rs = cart.getCustomer(customerid)
  If rs.RecordCount > 0 Then
    Response.Write ("Hello " & rs(0) & " " & rs(1))
    Response.Write(" - If this isn't you, please <a href='getnewcust.asp'>Login to your account</a>")
    cartid = cart.getCartID(customerID)
    If cartid = 0 Then
   	cartid = ""
    End If 
   End If
ElseIf cartid = "" AND customerid = "" Then 
  Response.Write("<a href='getcust.htm'>Login to your Account to retrieve an existing cart</a>")
End If

If cartid = "" Then
  	Response.Write("<br>Currently, your shopping cart is empty")
Else
    Set rs = cart.getItemTotals(cartid)
    If rs.RecordCount > 0 Then
       rs.MoveFirst
       Response.Write("<br>Currently you have <strong>" & rs(0) & "</strong> items in your cart ")
       Response.Write("for a total of <strong>$" & FormatNumber(rs(1),2) & "</strong> dollars.")
    End If
 End If

If the cart Cookie is empty (no cart is set on the host computer), a message to this effect is shown in the page; otherwise the summary of the cart contents is printed out. In addition, the name of the person who owns the current cart is displayed, and the Web shopper is given the option of logging into the system.

Information about the customer is returned with a new stored procedure, SP_GETCUSTOMER . The login portion of this shopping cart application is included – as an extra bit of bonus code! – with the sample code attached to this article.

In Summary

Web stores can be complicated applications, but the best approach to create a Web store is to break the store’s functionality into individual pieces, or applications, and implement each of these in turn.

A key aspect to the implementation strategy of the shopping cart is that it’s business logic should be kept as separate as possible from implementation and database details. Implementation details, such as ASP specific functionality, are handled within the ASP scripts, and data access is processed within stored procedures. With this approach, changes to either the implementation or the data schema impact little if at all on the shopping cart component itself.

This article took a look at one specific application of an online store, the shopping cart, and demonstrated how a cart can be implemented without a lot of complicated code using an ASP component written in Visual Basic, some ASP pages, and stored procedures. The sample code for download contains all of the code discussed in the article as well as other functionality to handle pulling the entire application together.