by Mark Trescowthick - GUI Computing
I was asked the question recently "How do I do a Shopping Cart in ASP?". My standard answer is that the best way is to create a VB server-side DLL to act as a cart object, and manipulate that object at session scope from within ASP. In this case, though, that answer wasn't appropriate for a number of environmental reasons.
And, in any case, it ought to be easy enough to do this just using Session variables, I figured. In fact, I was surprised that I couldn't find an example somewhere on the Web. So I volunteered to write a tiny demo for the questioner.
Well, of course, these 'tiny demos' are almost never as easy as you'd think - but this one was, in most cases, the exception that proved the rule. Happy? You bet!
What you'd really like to do is create 'collections' of shopping cart items, where each 'collection' represents one order in the cart. Now, you could do this with cookies directly using a syntax something along the lines of Request.cookies("MyProduct")("Price") or whatever. That sort of looks like a collection in many ways, and I wish that Session variables were able to be managed in that way. But, of course, the Session object if just a cookie itself, so you can't work it that way at all.
But what I didn't want was a cart that was so inflexible that it used arrays with 'hard' indices to indicate various pieces of information. I really wanted this to be text driven, so that it would be portable.
The approach I took was to build myself an object model (hardly worthy of the term!) and then implement interfaces to that in the form of functions. Essentially, I figured, a shopping cart consisted of many cart items, each of which had a number of pieces of info (e.g. price, quantity). To make things flexible, I wanted to ensure that everything was parameter-driven.
So the first step was to create a global.asa, which I can use to set up and initialise this structure.
Basically, I set up what amount to Global Constants in session variables for the Number of Items in the Cart, the 'Cart Info Size' - which is the number of bits of info we collect about each item in the Cart - and the root name for the actual session array variable which will hold this info.
There's really not much to it, and only the Session_onStart is relevant here :
Sub Session_OnStart ' initialise "cart" description Session("siNumItems") = 0 ' initialise number of products in cart Session("siCartInfoSize") = 5 ' zero based number of pieces of info we're collecting Session("ssItemRoot") = "saCart" ' root name for each cart item's array dim arCartDescription () ' descriptions for each array element redim arCartDescription (Session("siCartInfoSize")) arCartDescription(0) = "ProductID" arCartDescription(1) = "ProductName" arCartDescription(2) = "OrderQuantity" arCartDescription(3) = "UnitPrice" arCartDescription(4) = "ExtendedPrice" arCartDescription(5) = "ShippingCost" Session("soCartDescription") = arCartDescription End Sub
Basically, my structure is to have a Session array (soCartDescription) which holds the name of each element in the Cart Item. Then, as new cart items (or products) are added, I can simply generate a new Session array for each one, holding only the data. Each array will be named by a number, which is tacked on to the Session variable ssItemRoot to provide a unique name.
Managing the Cart
All the Cart management code is in one include file (radically named as cart.inc!). As presented, it supports only the following functions, but this could (indeed would) obviously be expanded in a 'real' system. These are :
addCartItem. This adds a new empty item (i.e. Product) and returns the array name for this item.
deleteCartItem. This "deletes" an item, by the simple expedient of nulling all its array items. To simplify matters, I simply created a new array and inserted it into the appropriate Session variable.
getCartInfo. This routine is pretty much used only internally and, given the name of a piece of information (e.g. "ProductID"), returns the array offset for it.
getItemRoot. This routine is a simple wrapper around the ssItemRoot Session variable.
getItemCount. Like getItemRoot, a wrapper around a Session variable, in this case siNumItems.
getItemInfoValue. This routine returns the actual value in a named piece of info. I include it here to allow me to get on my soapbox. You'll note that arThisItem is not DIMed. That's not me being slack (hey, there's a first time for everything!) but because, of course, I can't DIM it correctly. As with DLLs, so with Session variables. And I really do think that DIMing it as a variant is more misleading in ASP than not DIMing it at all. Soapbox off.
Function getItemInfoValue (sArrayName, sInfoName) ' gets a value for one piece of information for a product arThisItem = Session(sArrayName) getItemInfoValue = arThisItem(getCartInfo(sInfoName)) End Function
setItemInfoValue. Unsurprisingly, this routine sets the value of a given named piece of info.
And that's about the story. Of course, I'd need to add the ability to update (and probably merge) Items, as well as plenty more. But the basics are all there, and it works quite nicely.
The application as presented here is minute - some might even say "quick and dirty" (they'd be right).
Basically, a single ASP (yes, you guessed it :- "cart.asp") is responsible for updating the Cart information and displaying one of two pages - either Add an Item or Manage the Cart.
The code is so self-explanatory I'll leave you to download the zip and have a look.
One more thing does need to be said. These are Session variables we're using. Which means that (assuming IIS defaults) after 900 seconds of inactivity, they'll all mysteriously disappear, and your app will collapse in an ungraceful heap.
Whenever you use Session variables, you should always include something along the lines of this "CheckSession.inc" :
<% 'Check that the session is still valid If Not Session("LoggedOn") Then Response.write "<h2>Action Cancelled</h2>" Response.write "Sorry, your session has expired.<br>" Response.write "<a href='../Login.html'>Click here to Log In again</a>" Response.End End If %>
Of course, in this case my example doesn't extend that far, but you'd be surprised just how often this little gotcha will rear its ugly head!