CIS Logo SVC Logo

   Computing & Information Systems
   Department

 

Schoology Facebook        Search CIS Site      Tutorials

Cars App 2



Overview



What do we want to create here? The idea is to add functionality to the previous web app, larger e-commerce web app 1. Just add on to that web app (or a copy of that web app). In particular, we add the following features: First, we add product attributes. For example, the customer might select the attribute red in the color category for a particular product (car). Or, the customer might select leather for the seat covering category. We can have as many such categories as we wish. Second, we add a shopping cart and the associated checkout process. What good is an e-commerce app unless we can buy things?

Parts of the design and implementation of this web app are adapted from the book .NET E-Commerce Programming by Mike Gunderloy and Noel Jerke, Sybex (2002). Although this book appears to be out of print, get a copy if you can find one as it gives several rather complete e-commerce apps as examples, including many features not shown here (such as the separate management app mentioned below).

Let's start by taking a tour in pictures of the new web app.

  • The home page is shown in this screen shot. It looks much the same as before but notice the addition of a link for the shopping cart.
  • The brands link takes us to a screen for choosing the brand (model) of car from a drop-down list. This is the same as in the previous app.
  • Thus the next step is obviously to select a particular brand.
  • This brings us to the cars page where we select a particular model of car.
  • Doing so brings us to the selected product (Results) page where we can see the details about this product, things such as the price and description.
  • In addition, this page has us choose the attributes in each available category so as to customize the car to our wishes. We can even specify that we want more than one of this particular car by using the quantity drop-down list. (If we would choose a quantity of 3, for example, all 3 cars would have the same color and other customizable features. If we want 3 of the same model, but with different colors and other features, then we should select a quantity of 1, add that car to the shopping cart, then go to the brands and cars pages to choose the same car. This time we would customize it to a different color, etc.)
  • When you select a customizable feature, a drop-down list appears giving the possible values for this feature.
  • Once we have customized each available feature, the Add to Cart button appears. Note that the chosen attributes are added to the description box so that we can easily see what we have chosen.
  • If we decide to do so, the next step is to click on the "Add to Cart" button. This brings us to the cart page. Here we can see many of the details about every item that we have addded to the shopping cart, including brand, model, quantity, price, total cost, and the attributes that we selected for the customizable features. The description is not shown because of limited space. This screen also gives you the total number of items in the cart as well as the subtotal (without tax and shipping).
  • On this page we can click on the Remove button for any particular item. This removes that row from the cart.
  • There is also an "Empty the Cart" button which is used to remove all of the items in the shopping cart. The result is an empty cart.
  • You can return to the brands page (or use search) to look up additional cars and add them to your shopping cart. The cart shows up to 3 items per screen with paging so that you can easily click on Next to see the next group of items.
  • Of course, the cart page also has a button labelled "Buy Items in Cart". Clicking this takes you to the shipping page. This page uses required field validators to force you to enter data for all required fields.
  • The shipping page also uses a custom validator that forces you to select one of the states from the pull-down.
  • Regular expression validators are also used on the text boxes so that you cannot enter unusual characters into these.
  • The next step is to click the submit button on the shipping page. This takes you to the payment page where you enter your credit card data.
  • After submitting your payment information (assuming that your credit card payment was accepted), the order confirmation page is shown and this particular shopping trip is over. However, the user can click on Brands or Search, add cars to a new shopping cart, and do another purchase.
  • The other pages in the new web app look and function much like they did in the previous web app.
    • The search page lets you look up cars based on a target search string.
    • The search results page shows the matching items in a data grid with paging (in case all of the items don't fit on one page). Clicking on the View button in a row of this grid takes you to the selected product page that was described above.
  • You might wonder how orders are processed. The assumption here is that there is a separate management application used by those who run the web site. This management app allows a manager to see the shipping and payment information and lets the manager clear out the credit card information once the order is processed. (It is not good to keep credit card information longer than necessary since attackers love to steal this!)
  • An alternative design would be to have some sort of automatic processing of orders so that human intervention is not normally required.

Database Work



Product Table

  • The Product table from the previous web app should work fine.

Category Table

  • This table, too, should work unchanged.

CategoryProduct Table

  • This table will work unchanged unless you added new car data to the Product table.
  • If you did add new data to the Product table, then add rows to this table to indicate which of the new cars belong in which categories. (Since each category is a car manufacturer, a given model of car should only belong to one category. With products other than the cars you might want a particular product to belong to more than one category.)

AttributeCategory Table

  • This table is used to specify an ID number and a name for each category of attributes for our products (cars).
  • We use just 2 categories: color and seat covering. Then the user might choose the red attribute for color and the leather attribute for seat covering, for example.
  • Right click on Tables in Server Explorer and select Add New Table.
  • Then design this table as shown below.
Column Name Data Type Allow Nulls
AttributeCategoryNum int no
AttributeCategoryName varchar(16) no

  • Right click on the margin in front of the AttributeCategoryNum field and set it to be the primary key field.
  • Also set the properties shown at the bottom to make this same field an identity field so that the first value starts at 1, the next is 2, etc.
  • Do File, Save to save the table, nameing in AttributeCategory.
  • You can then close the table design window.
  • Then right click on the AttributeCategory table in Server Explorer and select Open Table Data.
  • Add the following data on our 2 categories:
AttributeCategoryNum AttributeCategoryName
1 Color
2 Seat Covering

Attribute Table

  • This table contains the ID numbers and names for each attribute (such as red, silver, purple, leather, cloth, etc.) as well as the AttributeCategoryNum identifying which category each of these attributes belong to. (It may be obvious to us that red, silver, and purple belong to the Color category while leather and cloth belong to the Seat Covering category, but we have to lay this out in our database.)
  • Add a new table and design it like this:
Column Name Data Type Allow Nulls
AttributeNum int no
AttributeName varchar(16) no
AttributeCategoryNum int no

  • Make AttributeNum to be the primary key field.
  • Also make this an identity field.
  • Save the table under the name Attribute.
  • Close this window and use Open Table Data on the new table.
  • Then add the following data to it:
AttributeNum AttributeName AttributeCategoryNum
1 Cinnamon 1
2 Silver 1
3 Jet Black 1
4 Cream 1
5 Aqua 1
6 Red 1
7 Sky Blue 1
8 Purple 1
9 Leather 2
10 Plastic 2
11 Cloth 2

ProductAttribute Table

  • This table is used to link the Attribute and Product tables.
  • This table only uses 2 fields, ProdNum and AttributeNum, which together form the (combination) primary key.
  • If, for example, the table contains a row of data consisting of 2, 6 this indicates that product 2 (the Tornado model of car) can have attribute 6 (Red). Of course, the Attribute table tells us that attribute 6 has name Red and is in the category with ID number 1. The AttributeCategory table then tells us that category 1 has name Color.
  • Add a new table and design it like this:
Column Name Data Type Allow Nulls
ProdNum int no
AttributeNum int no

  • To make both fields to be a combination primary key, click on the margin in front of the first field, then use CTRL right click on the margin in front of the second field and select Set Primary Key.
  • Save your table under the name ProductAttribute.
  • Then add the following data:
ProdNum AttributeNum
1 1
1 2
1 3
1 4
1 7
1 9
1 11
2 1
2 3
2 4
2 6
2 8
2 9
2 11
3 2
3 3
3 4
3 5
3 6
3 7
3 10
3 11
4 4
4 5
4 7
6 1
5 2
5 3
5 4
5 6
5 8
5 9
5 11
6 2
6 3
6 4
6 5
6 7
7 2
7 3
7 4
7 5
7 7
8 2
8 3
8 4
8 6
8 8
8 9
8 11
9 4
9 6
9 8
10 4
10 5
10 6
10 7
10 9
10 10
10 11
11 4
11 5
11 6
11 7
11 9
11 10
11 11
12 4
12 5
12 6
12 7
12 10
12 11
13 1
13 4
13 6
13 10
13 11
14 2
14 4
14 5
14 6
14 7
14 9
14 10
14 11

  • Note that not all color attributes are available for all cars. For example, from the above table we can see that car 2 (the Tornado) has available colors 1, 3, 4, 6, an 8 out of possible colors 1 through 8. (The names of these colors, of course, can be found in the Attribute table.)
  • The same car, the Tornado, has available attributes 9 and 11 for the type of seat covering out of possible values of 9, 10, and 11.
  • In fact, some products may have no attributes available for a particular category. For example, car 4 (the Famtastic) has possible attributes of 4, 5, and 7 only. These are colors since the AttributeCategoryNum is 1 for each of these. Thus there are no choices of attributes for the seat covering category. Apparently this car comes with some standard seat covering that cannot be changed.

Basket Table

  • This table is used to hold general information about the shopping basket.
  • A separate BasketItem table will hold information about each item in the shopping basket.
  • Design the table as shown below.
Column Name Data Type Allow Nulls
BasketNum int no
ShopperNum int no
BasketNumItems int no
BasketOrderPlaced int no
BasketSubtotal money no
BasketTotal money no
BasketShipping money no
BasketTax money no

  • Make BasketNum the primary key.
  • Also make it an identity field.
  • Note that the BasketOrderPlaced field value indicates whether the order has been placed (value 1) or not (value 0).
  • Set up all fields except the first 2 to have a default value of 0. This is done by click on the margin in front of a particular field and then putting 0 into the property at the bottom of the screen labelled Default Value or Binding.
  • Save this table under the name Basket.
  • No data is added to the Basket table ahead of time. Our e-commerce app will add data when users create shopping baskets.

BasketItem Table

  • This table is used to hold information about an individual product in the shopping basket.
  • Design the table as shown below.
Column Name Data Type Allow Nulls
BasketItemNum int no
BasketNum int no
ProdNum int no
BasketItemPrice money no
BasketItemProdName varchar(24) no
BasketItemQuantity int no
BasketItemAttributes varchar(64) yes

  • Make BasketItemNum the primary key.
  • Also make it an identity field.
  • Give a default value of 0 to the BasketItemPrice and BasketItemQuantity fields.
  • Save this table under the name BasketItem.
  • No data is added to the BasketItem table ahead of time. Our e-commerce app will add data when users add products to their shopping baskets.
  • To give a better idea of what this data might look like, this Word file contains a sample of data that might be stored in this table. Remember, do not add this data to your table manually. All data is added to this table automatically by the e-commerce app.
  • In this sample data we can see, for example, that basket 4 has basket items 6 and 7, which hold 1 Kangaroo car and 3 Tornados, respectively.
  • Pay careful attention to how the attributes are stored. The Kangaroo in basket item 6 has an attributes field value of "Color,1,Red,6". This indictates category 1 (Color) and attribute 6 (Red) for this car. The attributes field value for the 3 identical Tornados is "Color,1,Jet Black,3;Seat Covering,2,Leather,9". This indicates that these have been customized to have Jet Black color (category 1 is color and color 3 is Jet Black) and Leather for the seat covering.
  • Note how the groups are separated by semicolons, while the 4 items in a group are separated by commas. We will need to process this attributes string later in the VB code for our e-commerce app.

Shopper Table

  • This table is used to hold the name and contact information on each shopper.
  • We might keep this data long term since it would allow us to send email or paper mail advertising products to our customers. Keeping this data would also enable us to implement user profiles (so that a user could submit a password and have the user's name, address, etc. automatically available). We skip user profiles here so as to keep this app from being overly long to present.
  • Design the table as follows:
Column Name Data Type Allow Nulls
ShopperNum int no
ShopperFirstName varchar(24) yes
ShopperLastName varchar(32) no
ShopperStreet varchar(32) yes
ShopperCity varchar(32) yes
ShopperState varchar(2) yes
ShopperZipCode varchar(15) yes
ShopperPhone varchar(30) yes
ShopperEmail varchar(48) yes

  • Make ShopperNum the primary key.
  • Also make it an identity field.
  • Save this table under the name Shopper.
  • No data is added to this table ahead of time. Our e-commerce app will add data when a shopper checks out.

Order Table

  • This table is used to hold the name and contact information on the shopper who is placing an order along with the date the order is placed and the basket ID number.
  • Design the table as follows:
Column Name Data Type Allow Nulls
OrderNum int no
ShopperNum int no
BasketNum int no
OrderFirstName varchar(24) yes
OrderLastName varchar(32) yes
OrderStreet varchar(32) yes
OrderCity varchar(32) yes
OrderState varchar(2) yes
OrderZipCode varchar(15) yes
OrderPhone varchar(30) yes
OrderEmail varchar(48) yes
OrderDateOrdered datetime no

  • Make OrderNum the primary key.
  • Also make it an identity field.
  • Save this table under the name Order.
  • Make the BasketNum field to be a unique key. This is to prevent the possibility of having two or more orders for the same basket. Here are the steps to follow in creating this unique constraint:
    • In the table definition window, click on the margin in front of the BasketNum field.
    • Click on Table Designer, Indexes/Keys (or click the Indexes/Keys button).
    • There should already be an entry named PK_Order. If you check its Columns property, you will see that OrderNum is listed there. That says that OrderNum is the primary key.
    • Click the Add button.
    • Click in the box to the right of the Columns property. You will then see a "..." button. Click on it to get the screen for choosing the column. Choose BasketNum, of course, and click OK.
    • Click in the box to the right of the Type property and choose Unique Key from the drop-down list.
    • Click on Close.
    • Save your table again since you have just made a change to it.
  • No data is added to this table ahead of time. Our e-commerce app will add data when a shopper checks out.
  • Although the address data is the same as that stored in the Shopper table, we might periodically purge old data from the Order table, whereas the Shopper table data is likely to be kept around for a longer time (so that we can send promotions to our customers and so that the customers might have user profiles).

Payment Table

  • This table is used to hold a customer's credit card information as well as the order number so as to link the credit card information to a particular order.
  • We plan to keep the information in this table only as long as necessary in order to verify the credit card information and process the order. This is to reduce our exposure to thieves interested in stealing credit card information.
  • Design the table as follows:
Column Name Data Type Allow Nulls
PaymentNum int no
OrderNum int no
PaymentCardType varchar(16) no
PaymentCardNumber varchar(30) no
PaymentExpirationDate varchar(24) no
PaymentCardOwnerName varchar(64) no

  • Make PaymentNum the primary key.
  • Also make it an identity field.
  • Save this table under the name Payment.
  • Make OrderNum a unique key much like above. This is to prevent the possibility of having two or more sets of payment information for the same order.
  • No data is added to this table ahead of time. Our e-commerce app will add data when a shopper checks out.

Relationships Between Tables

  • We should next set up the appropriate relationships between tables based on where we have foreign keys present.
  • For a reminder on how to set up this type of relationship, look at the directions in the survey app.
  • Set up the following relationships. In each case, check the screen shot to be sure you have the same thing. Detailed directions are only given for the first relationship.
    • The BasketItem table contains BasketNum as a foreign key since BasketNum is the primary key in the Basket table. Here are the details on how to set up this relationship:
      • Begin by opening the table definition for the table containing the foreign key. Thus, in this case, you would right click on the BasketItem table and select Open Table Definition.
      • Click on Table Designer, Relationships (or click the Relationships button). This gives you a dialog box showing the relationships and their properties.
      • Click the Add button.
      • Then click in the box to the right of the Tables and Columns Specification property. A button labelled with "..." then shows up. Click on it to get the Tables and Columns dialog box.
      • First select the other table in the left column, the so-called primary key table. In this case, the table containing the primary key is the Basket table.
      • Make the BasketNum primary key to be the only field displayed underneath this. If there are other fields shown, click in its box and use the pull-down to change the entry to None. That will clear out the unwanted value.
      • The righthand column should already have the correct foreign key table shown, namely the BasketItem table. The field shown under it should be the BasketNum field, the one that matches with the left column. Both versions of BasketNum should be on the same line.
      • Click OK when you have this set up correctly.
    • The BasketItem table also contains foreign key ProdNum since ProdNum is the primary key in the Product table. Check this screen shot showing this relationship. When finished with the BasketItem table, be sure to save it.
    • Next, open the Basket table. It contains foreign key ShopperNum since ShopperNum is the primary key in the Shopper table. Check this screen shot showing this relationship. When finished with the Basket table, be sure to save it.
    • Now open the Order table. It contains ShopperNum as a foreign key since ShopperNum is the primary key in the Shopper table. Check this screen shot showing this relationship.
    • The Order table also contains BasketNum as a foreign key since BasketNum is the primary key in the Basket table. Check this screen shot showing this relationship. When finished with the Order table, be sure to save it.
    • Open the Payment table. It contains foreign key OrderNum since OrderNum is the primary key in the Order table. Check this screen shot showing this relationship. When finished with the Payment table, be sure to save it.
    • Next, open the ProductAttribute table. It contains foreign key ProdNum, which is the primary key in the Product table. Check this screen shot showing this relationship.
    • The ProductAttribute table also contains foreign key AttributeNum since AttributeNum is the primary key in the Attribute table. Check this screen shot showing this relationship. When finished with the ProductAttribute table, be sure to save it.
    • Finally open the Attribute table. It contains AttributeCategoryNum as a foreign key since AttributeCategoryNum is the primary key in the AttributeCategory table. Check this screen shot showing this relationship. When finished with the Attribute table, be sure to save it.

Work in Visual Studio



GlobalProcedures.vb

  • When a user first reaches our e-commerce site (probably at the home page, though possibly at some other page if the person has a bookmark to it) we wish to assign the user a shopper number.
  • We thus want to run the same code at the start of most of our pages. This code should check to see if the user has a shopper number. If not, a new shopper number should be assigned.
  • To insert a new row into the shopper table and return the new shopper number, we use the following stored procedure. Create it in Server Explorer and save it once the code looks like this.

CREATE PROCEDURE db_datareader.NewShopper
AS
    INSERT INTO Shopper(ShopperLastName) VALUES('')
    SELECT @@IDENTITY
    RETURN

  • Notice that the NewShopper stored procedure simply inserts a row with an empty string as the last name. (The other fields will be NULLS.) NewShopper then returns @@IDENTITY, the value from the identity field, which is ShopperNum for this table. Thus the new shopper number (1 more than the last one in this table) is returned.
  • On all of our web forms except Shipping.aspx, Payment.aspx, and Confirm.aspx make sure you have a label with ID ShopperErrorLabel and text (in red) "Error on page". Have the label's Visible property start off as False. We will make this label visible if we are somehow not able to handle the shopper number on a given page.
  • Next we need the to add a GlobalProcedures.vb file to our project. We will put a procedure in it that calls our NewShopper stored procedure. Right click on your project in Solution Explorer. Select Add, New Item, Code File. Then fill in GlobalProcedures.vb as the name for this file.
  • Place the following procedure into your new file.

Module GlobalProcedures

    Friend Sub CheckShopperNum(ByVal Session As HttpSessionState, ByRef ErrorFlag As Integer)
        Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
        Dim myCommand As SqlClient.SqlCommand = Nothing
        Dim SessionShopperNum As String

        ErrorFlag = 0

        Try
            SessionShopperNum = Session("ShopperNum")
            If (SessionShopperNum Is Nothing) OrElse (SessionShopperNum = "0") Then
                Dim connectionString As String = _
                    ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
                myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
                myCommand = New SqlClient.SqlCommand()
                myCommand.CommandType = CommandType.StoredProcedure
                myCommand.Connection = myDatabaseConnection
                myCommand.CommandText = "NewShopper"

                myDatabaseConnection.Open()
                Session("ShopperNum") = CType(myCommand.ExecuteScalar, String)
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If
        Catch exception As System.Data.SqlClient.SqlException
            ErrorFlag = 1
        Catch exception As Exception
            ErrorFlag = 1
        Finally    'Do cleanup tasks here:
            myCommand = Nothing

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
        End Try
    End Sub

End Module

  • As usual, make the connection strings name match the one in your Web.config file, not the one shown above.
  • The above code first checks to see if ShopperNum is stored in the session state. If so it does nothing further as the user already has a shopper number. If not, the code manually connects to the database and runs out NewShopper stored procedure. The returned new shopper number is stored in the session state. If the code raises an exception the ErrorFlag parameter is set to 1. The calling procedure can then use this to make the ErrorLabel visible.
  • Adjust the Page_Load procedures for all of our existing web forms (except Shipping, Payment, and Confirm) so that they start out by calling CheckShopperNum as follows.

Dim ErrorFlag As Integer

ShopperErrorLabel.Visible = False
CheckShopperNum(Session, ErrorFlag)
If ErrorFlag = 1 Then
    ShopperErrorLabel.Visible = True
End If

Code-Behind File for the Results Form

  • Your results form should have everything on it that is needed, but check that the Add to Cart button's Visible property is set to False.
  • Also, Label2's ID should be changed to CategoryLabel so as to match with the code below.
  • We need to change the code in results.aspx.vb so that it implements the desired new functionality.
  • Thus it should allow the user to select the quantity and to customize the various features. The attributes drop-down list should only appear after the user selects a category of customizable feature. The Add to Cart button should only appear after all of the features have been customized.
  • Change the Page_Load procedure to match the following.
  • As usual, change the name of the connection to match what is in your Web.config.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Dim Price As Decimal
    Dim Model As String
    Dim Brand As String
    Dim Description As String
    Dim Attributes As String
    Dim ProdNum, NumAttributes As Integer
    Dim Item As New ListItem
    Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
    Dim myCommand As SqlClient.SqlCommand = Nothing
    Dim myReader As SqlClient.SqlDataReader = Nothing
    Dim ErrorFlag As Integer

    ShopperErrorLabel.Visible = False
    CheckShopperNum(Session, ErrorFlag)
    If ErrorFlag = 1 Then
        ShopperErrorLabel.Visible = True
    End If

    AddToCartButton.Visible = False   ' until we are sure we should enable it

    If Not IsPostBack Then
        Attributes = ""
        If Session.Count = 0 Then   ' No session items were supplied.
            ErrorLabel.Visible = True
        Else
            Try
                Model = Session("ModelSelected")
                Brand = Session("BrandSelected")
                Dim connectionString As String = _
                    ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
                myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
                myCommand = New SqlClient.SqlCommand()

                'Set up to use a stored procedure:
                myCommand.CommandType = CommandType.StoredProcedure
                myCommand.Connection = myDatabaseConnection
                myCommand.CommandText = "SelectModelBrand"
                myCommand.Parameters.AddWithValue("@TargetModel", Model)
                myCommand.Parameters.AddWithValue("@TargetBrand", Brand)

                'Use an SqlDataReader to execute the stored procedure and get the results:
                myDatabaseConnection.Open()
                myReader = myCommand.ExecuteReader()
                If myReader.HasRows Then
                    myReader.Read()
                    Description = myReader.GetValue(0)
                    ProductDescriptionBox.Text = Description
                    ProductDescriptionBox.Visible = True
                    Session.Add("Description", Description)
                    Price = myReader.GetValue(1)
                    PriceLabel.Text = "Price:  " & String.Format("{0:C}", Price)
                    PriceLabel.Visible = True
                    Session.Add("Price", Price)
                    BrandLabel.Text = "Selected Brand: " & Brand
                    BrandLabel.Visible = True
                    ModelLabel.Text = "Selected Model: " & Model
                    ModelLabel.Visible = True
                    ProdNum = myReader.GetValue(2)
                    Session.Add("ProdNum", ProdNum)
                Else
                    ErrorLabel.Visible = True
                End If
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            Catch exception As System.Data.SqlClient.SqlException
                ErrorLabel.Visible = True
            Catch exception As Exception
                ErrorLabel.Visible = True
            Finally    'Do cleanup tasks here:
                myCommand = Nothing

                If Not myReader Is Nothing AndAlso _
                    Not myReader.IsClosed Then
                    myReader.Close()
                End If

                myReader = Nothing

                If Not myDatabaseConnection Is Nothing AndAlso _
                    myDatabaseConnection.State = ConnectionState.Open Then
                    myDatabaseConnection.Close()
                    myDatabaseConnection.Dispose()
                End If

                myDatabaseConnection = Nothing
            End Try

            Try
                Item.Text = "*** Make a choice ***"
                Item.Value = 0
                CategoryDropDownList.Items.Add(Item)

                Dim connectionString As String = _
                ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
                myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
                myCommand = New SqlClient.SqlCommand()

                'Set up to use a stored procedure:
                myCommand.CommandType = CommandType.StoredProcedure
                myCommand.Connection = myDatabaseConnection
                myCommand.CommandText = "SelectCategories"
                myCommand.Parameters.AddWithValue("@TargetProdNum", ProdNum)

                'Use an SqlDataReader to execute the stored procedure and get the results:
                myDatabaseConnection.Open()
                myReader = myCommand.ExecuteReader()
                While (myReader.Read())
                    Item = New ListItem
                    Item.Text = myReader.GetString(0)  'AttributeCategoryName
                    Item.Value = myReader.GetValue(1)  'AttributeCategoryNum
                    Item.Selected = False
                    CategoryDropDownList.Items.Add(Item)
                End While
                myReader.Close()

                NumAttributes = CategoryDropDownList.Items.Count - 1
                Session.Add("NumAttributes", NumAttributes)
                If NumAttributes > 0 Then
                    CategoryLabel.Visible = True   ' Had to rename Label2 as CategoryLabel
                    CategoryDropDownList.Visible = True
                Else
                    CategoryLabel.Visible = False
                    CategoryDropDownList.Visible = False
                    AddToCartButton.Visible = True
                End If

                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            Catch exception As System.Data.SqlClient.SqlException
                ErrorLabel.Visible = True
                CategoryLabel.Visible = False
                CategoryDropDownList.Visible = False
                AddToCartButton.Visible = False
            Catch exception As Exception
                ErrorLabel.Visible = True
                CategoryLabel.Visible = False
                CategoryDropDownList.Visible = False
                AddToCartButton.Visible = False
            Finally
                myCommand = Nothing

                If Not myReader Is Nothing AndAlso _
                    Not myReader.IsClosed Then
                    myReader.Close()
                End If

                myReader = Nothing

                If Not myDatabaseConnection Is Nothing AndAlso _
                    myDatabaseConnection.State = ConnectionState.Open Then
                    myDatabaseConnection.Close()
                    myDatabaseConnection.Dispose()
                End If

                myDatabaseConnection = Nothing
            End Try
        End If
    End If
End Sub

  • One of the first things that the above procedure does is to retrieve the brand and model selected from the session state.
  • It then uses these as parameters to pass to a stored procedure, SelectModelBrand, which is used to look up the details on this car. In particular, we get back the description, price, and product number. We display all but the product number on the results page and place all 3 values in the session state so that they will be available when we again need them.
  • This is almost the same stored procedure and mostly the same code so far that was used in the previous web app's result page. The one change to the stored procedure is that it must also select the product number.
  • Thus, in Server Explorer open the existing SelectModelBrand stored procedure and change it to the following. When you save it, the changes are made in the actual stored procedure in SQL Server.

ALTER PROCEDURE db_datareader.SelectModelBrand 
(
    @TargetModel varchar(24),
    @TargetBrand varchar(24)
)
AS
    SET NOCOUNT ON
    SELECT Product.ProdDescription, Product.ProdPrice, Product.ProdNum
    FROM Product INNER JOIN CategoryProduct 
    ON Product.ProdNum = CategoryProduct.ProdNum
    INNER JOIN Category ON CategoryProduct.CategoryNum = Category.CategoryNum
    WHERE (Product.ProdName = @TargetModel) AND (Category.CategoryName = @TargetBrand)
    RETURN

  • The above VB code has a second, Try...Catch block. It's job is to populate the drop-down list for the categories of customizable features.
  • The code runs a stored procedure to get the needed category names for the drop-down list.
  • Make a new stored procedure in the usual way. The code for it follows:

CREATE PROCEDURE db_datareader.SelectCategories
    (
    @TargetProdNum int
    )
AS
    SET NOCOUNT ON
    SELECT DISTINCT AttributeCategory.AttributeCategoryName, AttributeCategory.AttributeCategoryNum
    FROM Attribute INNER JOIN AttributeCategory 
    ON Attribute.AttributeCategoryNum = AttributeCategory.AttributeCategoryNum
    INNER JOIN ProductAttribute ON Attribute.AttributeNum = ProductAttribute.AttributeNum
    WHERE (ProductAttribute.ProdNum = @TargetProdNum) 
    ORDER BY AttributeCategory.AttributeCategoryName
    RETURN

  • Notice how the VB code uses variable Item of type ListItem to add one-by-one the items to the drop-down list, starting with the hard-coded "*** Make a choice ***".
  • The rest of the items are the category names retrieved from the database by the above stored procedure. Note that the stored procedure uses as an input parameter the ProdNum looked up by the previous stored procedure.
  • NumAttributes is assigned 1 less than the number of items in the drop-down list. This is because the first item in the list is not an attribute. Rather, it is "*** Make a choice ***". This value is saved in the session state so that it will be available later.
  • If there are no attributes, then we don't make the drop-down list visible. Instead, we just make the Add to Cart button visible as there are no customizable features for this product.
  • The next step is to add a procedure to handle things when the user selects a category from this drop-down list. Here is the code to add (to the same Results.aspx.vb file). Remember to adjust the connection string line in the usual manner.

Protected Sub CategoryDropDownList_SelectedIndexChanged(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles CategoryDropDownList.SelectedIndexChanged

    Dim Item As New ListItem
    Dim AttributeName As String
    Dim AttributeNum As Integer
    Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
    Dim myCommand As SqlClient.SqlCommand = Nothing
    Dim myReader As SqlClient.SqlDataReader = Nothing

    AddToCartButton.Visible = False
    AttributeName = CategoryDropDownList.SelectedItem.Text
    AttributeNum = CType(CategoryDropDownList.SelectedItem.Value, Integer)

    If Not Equals(AttributeName, "*** Make a choice ***") Then
        Session.Add("AttributeName", AttributeName)
        Session.Add("AttributeNum", AttributeNum)
        ValueDropDownList.Items.Clear()   'Make sure nothing remains in this list.

        Try
            Item.Text = "*** Make a choice ***"
            Item.Value = 0
            ValueDropDownList.Items.Add(Item)

            Dim connectionString As String = _
            ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            myCommand = New SqlClient.SqlCommand()

            'Set up to use a stored procedure:
            myCommand.CommandType = CommandType.StoredProcedure
            myCommand.Connection = myDatabaseConnection
            myCommand.CommandText = "SelectCategoryValues"
            myCommand.Parameters.AddWithValue("@TargetCategoryNum", AttributeNum)
            myCommand.Parameters.AddWithValue("@TargetProdNum", Session("ProdNum"))

            'Use an SqlDataReader to execute the stored procedure and get the results:
            myDatabaseConnection.Open()
            myReader = myCommand.ExecuteReader()
            While (myReader.Read())
                Item = New ListItem
                Item.Text = myReader.GetString(0)  'AttributeCategoryName
                Item.Value = myReader.GetValue(1)  'AttributeCategoryNum
                Item.Selected = False
                ValueDropDownList.Items.Add(Item)
            End While
            myReader.Close()

            If ValueDropDownList.Items.Count > 1 Then
                ValueLabel.Visible = True
                ValueDropDownList.Visible = True
            Else   ' This case should not occur.
                ValueLabel.Visible = False
                ValueDropDownList.Visible = False
            End If

            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()
        Catch exception As Exception
            ErrorLabel.Visible = True
            ValueLabel.Visible = False
            ValueDropDownList.Visible = False
            AddToCartButton.Visible = False
        Finally
            myCommand = Nothing

            If Not myReader Is Nothing AndAlso _
                Not myReader.IsClosed Then
                myReader.Close()
            End If

            myReader = Nothing

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
        End Try
    Else   ' No selection was made in the drop-down list.
        ValueDropDownList.Visible = False
        ValueLabel.Visible = False
    End If
End Sub

  • The first thing that the above procedure does is to get the AttributeName and AttributeNum from CategoryDropDownList.
  • As long as the user selected an actual category (instead of "*** Make a choice ***"), we proceed.
  • The AttributeName and AttributeNum values are saved in session state so that they will be available after a postback. (See the ValueDropDownList_SelectedIndexChanged procedure below.)
  • The code next runs a stored procedure to obtain from the database the names of the attribute values for the selected attribute category and product.
  • Thus, create the following stored procedure:

CREATE PROCEDURE db_datareader.SelectCategoryValues 
    (
    @TargetCategoryNum int,
    @TargetProdNum int
    )
AS
    SET NOCOUNT ON
    SELECT DISTINCT Attribute.AttributeName, Attribute.AttributeNum FROM Attribute
    INNER JOIN ProductAttribute ON Attribute.AttributeNum = ProductAttribute.AttributeNum
    WHERE (Attribute.AttributeCategoryNum = @TargetCategoryNum) 
    AND (ProductAttribute.ProdNum = @TargetProdNum)
    ORDER BY Attribute.AttributeName
    RETURN

  • Note that this stored procedure returns the name and ID number for each attribute for the desired product and category of attribute.
  • The VB code uses our data reader to add the name of each applicable attribute to ValueDropDownList.
  • If there are no items in this list other than "*** Make a choice ***", we do not make it visible. Because of the way we generated the list of categories, this case should not occur. However, it is handled here just in case we missed something!
  • Next, we add code to handle things when the user selects a particular attribute value. Use the following:

Protected Sub ValueDropDownList_SelectedIndexChanged(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles ValueDropDownList.SelectedIndexChanged

    Dim Top, k, AttributeNum, ValueNum As Integer
    Dim Found As Boolean
    Dim AttributeName, ValueName As String
    Dim TempStr, NewStr, ItemsStr, NewDescription, Attributes, Description As String
    Dim AttributesArray As String() = Nothing
    Dim ItemsArray As String() = Nothing
    Dim delimStr As String = ";"
    Dim DelimStr2 As String = ","
    Dim delimiter As Char() = delimStr.ToCharArray()
    Dim delimiter2 As Char() = DelimStr2.ToCharArray()

    ValueName = ValueDropDownList.SelectedItem.Text
    ValueNum = CType(ValueDropDownList.SelectedItem.Value, Integer)

    If Not Equals(ValueName, "*** Make a choice ***") Then
        Found = False
        TempStr = ""
        Description = Session.Item("Description")
        Attributes = Session.Item("Attributes")
        AttributeName = Session.Item("AttributeName")
        AttributeNum = Session.Item("AttributeNum")

        'Adjust the Attributes string:
        If Not Attributes = "" Then
            AttributesArray = Attributes.Split(delimiter)
            Top = AttributesArray.GetUpperBound(0)
            For k = 0 To Top
                ItemsArray = AttributesArray(k).Split(delimiter2)
                If ItemsArray(0) = AttributeName Then  ' record new value here
                    ItemsArray(2) = ValueName
                    ItemsArray(3) = ValueNum
                    Found = True
                    ItemsStr = String.Join(",", ItemsArray)
                    If Not TempStr = "" Then
                        TempStr &= ";"
                    End If
                    TempStr &= ItemsStr
                ElseIf (Not Found) And (ItemsArray(0) > AttributeName) Then
                    NewStr = AttributeName & "," & AttributeNum & "," _
                        & ValueName & "," & ValueNum
                    If Not TempStr = "" Then
                        TempStr &= ";"
                    End If
                    TempStr &= NewStr
                    TempStr = TempStr & ";" & AttributesArray(k)
                    Found = True
                Else
                    If Not TempStr = "" Then
                        TempStr &= ";"
                    End If
                    TempStr &= AttributesArray(k)
                End If
            Next
        End If
        If Not Found Then   ' record new value at end of string
            If Not TempStr = "" Then
                TempStr &= ";"
            End If
            TempStr = TempStr & AttributeName & "," & AttributeNum & "," _
                & ValueName & "," & ValueNum
        End If

        Attributes = TempStr
        Session.Add("Attributes", Attributes)

        ' Loop through the entries in Attributes to set up NewDescription:
        AttributesArray = Attributes.Split(delimiter)
        Top = AttributesArray.GetUpperBound(0)
        NewDescription = Description
        For k = 0 To Top
            ItemsArray = AttributesArray(k).Split(delimiter2)
            NewDescription &= vbCrLf
            NewDescription &= ItemsArray(0)
            NewDescription &= ": "
            NewDescription &= ItemsArray(2)
        Next
        Session.Add("NumAttributesCustomized", Top + 1)
        ProductDescriptionBox.Text = NewDescription
    End If

    ' If the user has customized all of the features, show the "Add to Cart" button.
    If Session("NumAttributesCustomized") = Session("NumAttributes") Then
        AddToCartButton.Visible = True
    Else
        AddToCartButton.Visible = False
    End If
End Sub

  • The purpose of the above code is to save all of the attribute information in the session state, to show this attribute information in the product description box, and to make the Add to Cart button visible if the user has customized all of the attribute categories for this product.
  • The attribute information is kept in a string such as this example:

Color,1,Purple,8;Seat Covering,2,Cloth,11

  • Note that each set of 4 items is separated from the next set by a semicolon.
  • The items inside of each set are separated by commas.
  • The ordering of the 4 items is always the same: category name, category number, attribute name, attribute number.
  • This format makes it relatively easy to pick out the pieces of data that we need, plus it is flexible in that it allows us to handle a variable number of categories and attributes.
  • Our VB code above begins by extracting the attribute name (ValueName) from ValueDropDownList as well as the corresponding attribute nuber (ValueNum) from this list.
  • If the selected value is "*** Make a choice ***", then we skip most of the code and proceed to the end to check if all available categories have been customized. (If so, we make the Add to Cart button visible.)
  • Otherwise, we need to process the selected attribute value.
  • We look up in session state the recorded product description, attributes string (in the above format), selected attribute name, and the corresponding attribute number.
  • The purpose of the next section of code is to add the new attribute name and number (along with the selected category name and number) to the attribute string. The new attribute string is built up in TempStr and copied to the Attributes variable and session state once it is finished.
  • TempStr starts off as an empty string.
  • We use the Split method to put into AttributesArray the sets of items we get by splitting the Attributes string apart at the semicolons.
  • We then loop through this array to see if it already contains an entry for the selected category. If so, the user is trying to revise the attribute selected for this category (perhaps by changing the color of the car, for example). We set the Found flag to be true if we find such an entry and record the new attribute name and number at the same location in the AttributesArray.
  • Note that each time around this loop we do a second split, this time to break one set of items apart at the commas. This allows us to examine the 4 individual items: the category name, category number, attribute name, attribute number.
  • If we find the desired attribute name present, we record the new values in the appropriate 2 of the 4 items and then use a Join to paste the 4 pieces together into one string with commas between the pieces. This string is then appended to TempStr, with a semicolon preceding it if TempStr already has anything in it. Thus we gradually build up our revised attributes information in TempStr.
  • The ElseIf section inside of our loop is used to handle the case where we don't find the desired attribute name, but we have just reached an attribute name that is alphabetically larger than the attribute name for the data that we wish to add. In this case we manually concatenate the 4 pieces of data and append it to TempStr. Then we tack on the set of data for the alphabetically larger attribute name.
  • The Else section of this same loop is used in all other cases to simply append the same set of data taken from the AttributesArray to TempStr.
  • After the loop ends, if the Found flag is not true, then the new attribute data has not yet been added and needs to be concatenated to the end of TempStr. This could be either because we started with an empty Attributes string or because the selected category name (called AttributeName in the code) comes alphabetically after all of the category names already in the AttributesArray. In either case, we just manually concatenate the desired set of 4 items to the end of TempStr.
  • We then copy TempStr into the Attributes string and record the same data in the session state for later use.
  • We next loop through the Attributes data to extract the attributes information and append it in readable form to the product description.
  • This begins by splitting the Attributes string apart at the semicolons.
  • We loop through the resulting array and split each entry apart at the commmas.
  • For each entry we append the category and attribute names, in readable form, to the NewDescription string (which was initialized to contain the product description).
  • Thus by the end of the loop NewDescription contains the intial product description plus one or more lines of attribute information (such as "Color: Purple").
  • We then save in session state the number of categories that have been customized. This is 1 more than the top index used in the array (since indexing starts at 0).
  • NewDescription is copied into the product description text box.
  • If the number of categories customized then matches the number of categories (here somewhat misnamed as NumAttributes) then it is time to make the Add to Cart button visible; otherwise not.
  • The final procedure to add to the Results.aspx.vb file is one to handle the event of clicking on the Add to Cart button. You can generate the outline of this procedure by double clicking on the button on the web form while in Design view. Then adjust it to match the following (except that the connection string line must be adjusted as usual).

Protected Sub AddToCartButton_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles AddToCartButton.Click

    Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
    Dim cmdCreateBasket = New System.Data.SqlClient.SqlCommand
    Dim cmdInsertBasketItem = New System.Data.SqlClient.SqlCommand

    Try
        Dim connectionString As String = _
            ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
        myDatabaseConnection = New SqlClient.SqlConnection(connectionString)

        cmdCreateBasket.CommandText = "NewBasket"
        cmdCreateBasket.CommandType = System.Data.CommandType.StoredProcedure
        cmdCreateBasket.Connection = myDatabaseConnection
        cmdCreateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@RETURN_VALUE", _
            System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.ReturnValue, False, _
            CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
        cmdCreateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@ShopperNum", _
            System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
            CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
        cmdCreateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@BasketNum", _
            System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Output, False, _
            CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))

        cmdInsertBasketItem.CommandText = "InsertBasketItem"
        cmdInsertBasketItem.CommandType = System.Data.CommandType.StoredProcedure
        cmdInsertBasketItem.Connection = myDatabaseConnection
        cmdInsertBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter("@RETURN_VALUE", _
            System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.ReturnValue, False, _
            CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
        cmdInsertBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter("@BasketNum", _
            System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
            CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
        cmdInsertBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter("@ProdNum", _
            System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
            CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
        cmdInsertBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Price", _
            System.Data.SqlDbType.Money, 8, System.Data.ParameterDirection.Input, False, _
            CType(19, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
        cmdInsertBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter("@ProdName", _
            System.Data.SqlDbType.Char, 24))
        cmdInsertBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Quantity", _
            System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
            CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
        cmdInsertBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Attributes", _
            System.Data.SqlDbType.VarChar, 64))

        Session.Add("Quantity", CType(QuantityDropDownList.SelectedItem.Value, Integer))
        If CType(Session("BasketNum"), String) = "" Then   ' need to make a new basket
            cmdCreateBasket.Parameters("@ShopperNum").Value = Session("ShopperNum")
            myDatabaseConnection.Open()
            cmdCreateBasket.ExecuteNonQuery()
            Session.Add("BasketNum", cmdCreateBasket.Parameters("@BasketNum").Value)
            myDatabaseConnection.Close()
        End If

        'Now we have a basket, either new or old.
        cmdInsertBasketItem.Parameters("@BasketNum").Value = Session("BasketNum")
        cmdInsertBasketItem.Parameters("@ProdNum").Value = Session("ProdNum")
        cmdInsertBasketItem.Parameters("@Price").Value = Session("Price")
        cmdInsertBasketItem.Parameters("@ProdName").Value = Session("ModelSelected")
        cmdInsertBasketItem.Parameters("@Quantity").Value = Session("Quantity")
        cmdInsertBasketItem.Parameters("@Attributes").Value = Session("Attributes")
        myDatabaseConnection.Open()
        cmdInsertBasketItem.ExecuteNonQuery()
        myDatabaseConnection.Close()
        myDatabaseConnection.Dispose()
    Catch exception As Exception
        ErrorLabel.Visible = True
    Finally
        If Not myDatabaseConnection Is Nothing AndAlso _
            myDatabaseConnection.State = ConnectionState.Open Then
            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()
        End If

        myDatabaseConnection = Nothing
    End Try

    If ErrorLabel.Visible = False Then
        Response.Redirect("Cart.aspx")
    End If
End Sub

  • This code starts by getting the quantity desired of this product and recording it in the session state.
  • Then we check to see if a basket number exists in session state. If not, we have to create one for this shopper.
  • For this purpose we use a stored procedure named NewBasket. Create this stored procedure so that it matches the following:

CREATE PROCEDURE db_datareader.NewBasket 
    (
    @ShopperNum int,
    @BasketNum int OUTPUT
    )
AS
    INSERT INTO Basket(ShopperNum) VALUES(@ShopperNum)
    SELECT @BasketNum = @@IDENTITY 
    RETURN

  • This stored procedure inserts into the Basket table the shopper number passed in as a parameter. Then it returns the new basket number from the identity field.
  • Our above VB code fills in the current shopper number as the parameter to pass to this stored procedure.
  • It then manually executes the stored procedure and places the returned basket number into the session state.
  • At this point we have a basket number (either old or new).
  • Our code then calls another stored procedure named InsertBasketItem to insert the product information into the BasketItem table (or to update the appropriate entry in BasketItem if this exact product is already present for the current basket (shopping cart).
  • Create this stored procedure as follows:

CREATE PROCEDURE db_datareader.InsertBasketItem 
    (
    @BasketNum int,
    @ProdNum int,
    @Price money,
    @ProdName varchar(24),
    @Quantity int,
    @Attributes varchar(64)
    )
AS
    DECLARE @Current int
    SELECT @Current = COUNT(*) FROM BasketItem
        WHERE (BasketNum = @BasketNum) AND (ProdNum = @ProdNum) AND (BasketItemAttributes = @Attributes)
    IF @Current = 0   /* this product is not already in the basket */
        INSERT INTO BasketItem(BasketNum, ProdNum, BasketItemPrice, BasketItemProdName, 
		BasketItemQuantity, BasketItemAttributes)
        VALUES(@BasketNum, @ProdNum, @Price, @ProdName, @Quantity, @Attributes)
    ELSE   /* this exact product is already in the basket, so just update the quantity */
        UPDATE BasketItem SET BasketItemQuantity = BasketItemQuantity + @Quantity
        WHERE  (BasketNum = @BasketNum) AND (ProdNum = @ProdNum) AND (BasketItemAttributes = @Attributes)
    RETURN

  • All of the parameters to this stored procedure are input parameters.
  • Note how @Current is set up to count the number of rows in the BasketItem table that match the basket number, product number, and attributes string. This count should be 0 or 1. Either we already have this exact product (including the same choices for all of the customizable features) in this basket or we do not.
  • If this exact product is already present, we just update the quantity by adding on the new number to be purchased to the already existing quantity to be purchased.
  • Once our VB code executes this stored procedure, it redirects to the Cart.aspx page.
  • In order to execute the 2 stored procedures just shown, the VB code had to set up information about the parameters to be passed into and out of the stored procedure. This is because we were not using a data reader, data adapter, or some other high-level machinery to aid us. That is the reason for the Parameters.Add lines in the above VB code.

The Code-Behind File for the Cars Form

  • Look at your Cars.aspx.vb file.
  • As already mentioned, we have to call CheckShopperNum at the start of the Page_Load procedure.
  • Now that we have added the code to Results.aspx.vb for the Attributes string, we need to clear out that information in the code-behind file for the Cars form just before it redirects to the Results page. This is so that old data for a previous car is not used with the newly selected car.
  • The entire revised code for Page_Load is shown below. Remember to adjust the connection string line.
  • Note how before redirecting to the Results page, it puts the empty string into Attributes in session state and completely removes NumAttributes and NumAttributesCustomized from session state.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Dim TargetModel As String
    Dim ErrorFlag As Integer

    ShopperErrorLabel.Visible = False
    CheckShopperNum(Session, ErrorFlag)
    If ErrorFlag = 1 Then
        ShopperErrorLabel.Visible = True
    End If

    If Not IsPostBack Then
        Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
        Dim myCommand As SqlClient.SqlCommand = Nothing
        Dim myReader As SqlClient.SqlDataReader = Nothing

        Try
            Dim connectionString As String = _
                ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            myCommand = New SqlClient.SqlCommand()
            BrandLabel.Text = "Brand: " & Session("BrandSelected")
            CarsList.Items.Add("*** Make a choice ***")

            'Set up to use a stored procedure:
            myCommand.CommandType = CommandType.StoredProcedure
            myCommand.Connection = myDatabaseConnection
            myCommand.CommandText = "SelectProdName"
            myCommand.Parameters.AddWithValue("@TargetBrand", Session("BrandSelected"))

            'Use an SqlDataReader to execute the stored procedure and
            'get the results into the drop down list:
            myDatabaseConnection.Open()
            myReader = myCommand.ExecuteReader()
            While (myReader.Read())
                CarsList.Items.Add(myReader.GetString(0))
            End While

            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()
        Catch exception As System.Data.SqlClient.SqlException
            ErrorLabel.Visible = True
        Catch exception As Exception
            ErrorLabel.Visible = True
        Finally    'Do cleanup tasks here:
            myCommand = Nothing

            If Not myReader Is Nothing AndAlso _
                Not myReader.IsClosed Then
                myReader.Close()
            End If

            myReader = Nothing

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
        End Try
    Else   ' We have a postback.
        TargetModel = CarsList.SelectedItem.Text
        If Not Equals(TargetModel, "*** Make a choice ***") Then
            Session.Add("ModelSelected", TargetModel)
            Session.Add("Attributes", "") _
            ' To be sure that attributes for another product are cleared
            Session.Remove("NumAttributes")
            Session.Remove("NumAttributesCustomized")
            Response.Redirect("Results.aspx")
        End If
    End If
End Sub

The Code-Behind File for the SearchResults Form

  • In the same way, the code-behind file Searchresults.aspx.vb must be modified just before it redirects to the Results page.
  • Here is the modified procedure. The rest of this file stays the same.

Protected Sub ResultsGridView_ItemCommand(ByVal Source As Object, _
    ByVal e As GridViewCommandEventArgs) Handles ResultsGridView.RowCommand

    If e.CommandName = "View" Then
        ' Find the index of the row where the user clicked the View button:
        Dim index As Integer = Convert.ToInt32(e.CommandArgument)
        ' Find the model and brand of the item in this row:
        Session.Add("ModelSelected", ResultsGridView.Rows(index).Cells(1).Text)
        Session.Add("BrandSelected", ResultsGridView.Rows(index).Cells(2).Text)
        Session.Add("Attributes", "")  'To clear any attributes from an old product
        Session.Remove("NumAttributes")
        Session.Remove("NumAttributesCustomized")
        Response.Redirect("Results.aspx")
    End If
End Sub

The Shopping Cart Form

  • The Cart.aspx form was already created for you. However, the label for the database error situation should be given the ID DatabaseErrorLabel so that it matches the code below.
  • Double click on each button to create the outline of a click handler for each. We will fill in the code for these shortly.

The Code-Behind File for the Shopping Cart Form

  • In the Cart.aspx.vb file set up the Page_Load procedure to look like the following.
  • Of course, change the name of the connection to match what is in your Web.config file.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Dim ErrorFlag As Integer
    Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
    Dim cmdCreateBasket As System.Data.SqlClient.SqlCommand = Nothing

    If Not IsPostBack Then
        ShopperErrorLabel.Visible = False
        CheckShopperNum(Session, ErrorFlag)
        If ErrorFlag = 1 Then
            ShopperErrorLabel.Visible = True
        End If

        Try
            Dim connectionString As String = _
                ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            cmdCreateBasket = New System.Data.SqlClient.SqlCommand()

            cmdCreateBasket.CommandText = "NewBasket"
            cmdCreateBasket.CommandType = System.Data.CommandType.StoredProcedure
            cmdCreateBasket.Connection = myDatabaseConnection
            cmdCreateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@RETURN_VALUE", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.ReturnValue, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdCreateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@ShopperNum", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdCreateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@BasketNum", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Output, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))

            If CType(Session("BasketNum"), String) = "" Then   ' need to make a new basket
                cmdCreateBasket.Parameters("@ShopperNum").Value = Session("ShopperNum")
                myDatabaseConnection.Open()
                cmdCreateBasket.ExecuteNonQuery()
                Session.Add("BasketNum", cmdCreateBasket.Parameters("@BasketNum").Value)
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If
        Catch exception As Exception
            ErrorLabel.Visible = True
        Finally
            cmdCreateBasket.Dispose()

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
            cmdCreateBasket = Nothing
        End Try

        UpdatePage()
    End If
End Sub

  • The above procedure does our usual CheckShopperNum so as to generate a new shopper number if the user doesn't already have one.
  • The next section checks to see if there is a shopping basket number. If not, it runs our NewBasket stored procedure to create a basket number. This is the same code that we used above on the results page when the user clicked the Add to Cart button. So, the user should already have a basket number, but we want to be sure that there is a basket number no matter how the user reached the shopping cart page.
  • Finally, the above code calls an UpdatePage procedure (which we write below) to place the appropriate information about the basket items on the shopping cart page.
  • In fact, the following is the code for this UpdatePage procedure. Go ahead and add it, adjusting the connection string line in the usual way.

Private Sub UpdatePage()
    Dim BasketNum, Quantity As Integer
    Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
    Dim myCommand As SqlClient.SqlCommand = Nothing
    Dim adapter As SqlClient.SqlDataAdapter = Nothing
    Dim dTable As DataTable = Nothing
    Dim cmdBasketQuantitySubTotal As System.Data.SqlClient.SqlCommand = Nothing

    If Session.Count = 0 Then   ' No session items were supplied.
        ErrorLabel.Visible = True
    Else
        Try
            Dim connectionString As String = _
                ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
            dTable = New DataTable
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            myCommand = New SqlClient.SqlCommand()

            'Set up to get the basket data via a stored procedure:
            BasketNum = Session("BasketNum")
            myCommand.CommandType = CommandType.StoredProcedure
            myCommand.Connection = myDatabaseConnection
            myCommand.CommandText = "GetBasket"
            myCommand.Parameters.AddWithValue("TargetBasketNum", BasketNum)
            adapter = New SqlClient.SqlDataAdapter(myCommand)

            ' Put the basket data into dTable:
            myDatabaseConnection.Open()
            adapter.Fill(dTable)
            myDatabaseConnection.Close()

            If dTable.Rows.Count = 0 Then
                ItemsLabel.Text = "0 items in cart"
                CartGridView.Visible = False
                SubTotalLabel.Visible = False
                BuyButton.Visible = False
                EmptyButton.Visible = False
            Else
                AdjustAttributes(dTable)
                Session.Add("DataTable", dTable)  ' Save the table for easy paging.
                CartGridView.DataSource = dTable
                CartGridView.DataBind()
                CartGridView.Visible = True
                SubTotalLabel.Visible = True
                BuyButton.Visible = True
                EmptyButton.Visible = True

                ' Get the subtotal and the quantity of items in the cart (basket):
                cmdBasketQuantitySubTotal = New System.Data.SqlClient.SqlCommand
                cmdBasketQuantitySubTotal.CommandText = "BasketQuantitySubTotal"
                cmdBasketQuantitySubTotal.CommandType = System.Data.CommandType.StoredProcedure
                cmdBasketQuantitySubTotal.Connection = myDatabaseConnection
                cmdBasketQuantitySubTotal.Parameters.Add(New System.Data.SqlClient.SqlParameter( _
                    "@RETURN_VALUE", System.Data.SqlDbType.Int, 4, _
                    System.Data.ParameterDirection.ReturnValue, False, CType(10, Byte), _
                    CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
                cmdBasketQuantitySubTotal.Parameters.Add(New System.Data.SqlClient.SqlParameter( _
                    "@TargetBasketNum", System.Data.SqlDbType.Int, 4, _
                    System.Data.ParameterDirection.Input, False, CType(10, Byte), _
                    CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
                cmdBasketQuantitySubTotal.Parameters.Add(New System.Data.SqlClient.SqlParameter( _
                    "@Quantity", System.Data.SqlDbType.Int, 4, _
                    System.Data.ParameterDirection.Output, False, CType(10, Byte), CType(0, Byte), _
                    "", System.Data.DataRowVersion.Current, Nothing))
                cmdBasketQuantitySubTotal.Parameters.Add(New System.Data.SqlClient.SqlParameter( _
                    "@SubTotal", System.Data.SqlDbType.Money, 8, _
                    System.Data.ParameterDirection.Output, False, CType(19, Byte), CType(0, Byte), _
                    "", System.Data.DataRowVersion.Current, Nothing))

                cmdBasketQuantitySubTotal.Parameters("@TargetBasketNum").Value = BasketNum
                myDatabaseConnection.Open()
                cmdBasketQuantitySubTotal.ExecuteNonQuery()
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()

                SubTotalLabel.Text = "Subtotal (before tax & shipping): " & _
                    Format(cmdBasketQuantitySubTotal.Parameters("@SubTotal").Value, "Currency")
                Quantity = cmdBasketQuantitySubTotal.Parameters("@Quantity").Value
                If Quantity = 1 Then
                    ItemsLabel.Text = "1 item in cart"
                Else
                    ItemsLabel.Text = Quantity.ToString & " items in cart"
                End If
            End If
        Catch exception As System.Data.SqlClient.SqlException
            DatabaseErrorLabel.Visible = True
        Catch exception As Exception
            ErrorLabel.Visible = True
            ' ErrorLabel.Text = exception.Message   ' Uncomment this line for debugging.
        Finally
            dTable.Dispose()
            adapter.Dispose()
            myCommand.Dispose()

            If Not cmdBasketQuantitySubTotal Is Nothing Then
                cmdBasketQuantitySubTotal.Dispose()
            End If

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
            dTable = Nothing
            adapter = Nothing
            myCommand = Nothing
            cmdBasketQuantitySubTotal = Nothing
        End Try
    End If
End Sub

  • This UpdatePage procedure first checks if there is any session state information. If not, it simply displays an error message in a label.
  • Otherwise, it goes on to try to look up and display the contents of the shopping cart. If this does not work, an exception is raised and an error label is made visible.
  • To retrieve the shopping cart data we use a selection type of stored procedure. Thus, create the following stored procedure:

CREATE PROCEDURE db_datareader.GetBasket 
    (
    @TargetBasketNum int
    )
AS
    SET NOCOUNT ON
    SELECT BasketItem.ProdNum, BasketItem.BasketItemProdName,
    BasketItem.BasketItemAttributes, BasketItem.BasketItemPrice,
    BasketItem.BasketItemQuantity, BasketItem.BasketItemNum, Category.CategoryName, 
    BasketItem.BasketItemQuantity * BasketItem.BasketItemPrice AS BasketItemTotal
    FROM CategoryProduct
    INNER JOIN Category ON CategoryProduct.CategoryNum = Category.CategoryNum
    INNER JOIN Product ON CategoryProduct.ProdNum = Product.ProdNum
    INNER JOIN BasketItem ON Product.ProdNum = BasketItem.ProdNum
    WHERE (BasketItem.BasketNum = @TargetBasketNum)
    RETURN

  • Note the one computed value with alias BasketItemTotal. It is computed as the quantity times the price.
  • The stored procedure returns the desired data for all rows in the joined table that have the target basket ID number. Thus we get rows of data for all of the items in the shopping basket.
  • Back in the VB code that calls this stored procedure, note how it looks up the basket number from the session state and fills this in as the parameter to the GetBasket stored procedure.
  • If the stored procedure returns no data, we display a "0 items in cart" message on a label.
  • Otherwise, we display the shopping cart data in the grid view, though with some alteration to the attribute information (carried out by the AdjustAttributes procedure) so that it is more readable.
  • Note that we also save dTable in session state. That is to allow paging to work a little faster. When the user goes to a new page in the grid view, no data needs to be retrieved from the database, as it is already in session state.
  • To get the subtotal and number (quantity) of items in the shopping cart, we call another stored procedure. This one is named BasketQuantitySubTotal and is passed the basket ID number by our VB code. The subtotal is, of course, formatted as currency for display on the appropriate label.
  • The BasketQuantitySubTotal stored procedure should be created as follows:

CREATE PROCEDURE db_datareader.BasketQuantitySubTotal 
    (
    @TargetBasketNum int,
    @Quantity int OUTPUT,
    @SubTotal money OUTPUT
    )
AS
    SELECT @SubTotal = SUM(BasketItemQuantity * BasketItemPrice)
        FROM BasketItem WHERE BasketNum = @TargetBasketNum
    SELECT @Quantity = SUM(BasketItemQuantity)
        FROM BasketItem WHERE BasketNum = @TargetBasketNum 
    RETURN

  • This stored procedure sums up the quantity times price for each row of data in the BasketItem table having the target basket number. This then is the subtotal for this basket.
  • Similarly, it sums up the quantity for each row in the same table that has the desired basket number. This is the total number of items in the shopping basket.
  • Next we add the AdjustAttributes VB procedure that we used above:

' Adjusts the Attributes column in dTable so that the data there is more readable.
Private Sub AdjustAttributes(ByRef dTable As DataTable)
    Dim Row As DataRow
    Dim AttrString As String
    Dim AttributesArray As String() = Nothing
    Dim ItemsArray As String() = Nothing
    Dim delimStr As String = ";"
    Dim DelimStr2 As String = ","
    Dim delimiter As Char() = delimStr.ToCharArray()
    Dim delimiter2 As Char() = DelimStr2.ToCharArray()
    Dim k, Top As Integer

    For Each Row In dTable.Rows
        AttrString = Row.Item("BasketItemAttributes")
        AttributesArray = AttrString.Split(delimiter)
        Top = AttributesArray.GetUpperBound(0)
        AttrString = ""
        For k = 0 To Top
            If k <> 0 Then
                AttrString &= ", "
            End If
            ItemsArray = AttributesArray(k).Split(delimiter2)
            AttrString &= ItemsArray(0)
            AttrString &= ": "
            AttrString &= ItemsArray(2)
        Next
        Row.Item("BasketItemAttributes") = AttrString
    Next
End Sub

  • AdjustAttibutes changes the data in the BasketItemAttributes column of the data table dTable passed in as a parameter.
  • Since a data table is just an in-memory copy of a table from our database, this does not alter anything in the database.
  • A "For Each" loop is used to loop through all of the rows in dTable.
  • With each row we look up the BasketItemAttributes field value and place it in the AttrString variable. This is the field that contains a string such as this example that we looked at before:

Color,1,Purple,8;Seat Covering,2,Cloth,11

  • Our code splits this string apart at the semicolons, putting the pieces into AttributesArray.
  • We then loop through all of the pieces in AttributesArray using a "For" loop.
  • For each such piece we split it apart at the commas.
  • We use items 0 and 2 from the results, as they are the category name and attribute name (such as Color and Purple from the first piece of data in the above example). These two names are concatenated to a growing string of readable attribute information named AttrString. (Note that we reused AttrString by assigning to it the empty string before this new use of it.)
  • At the end of the inner loop, the nicely formatted AttrString contains something like "Color: Red" and is assigned into the BasketItemAttributes field of the current row of dTable.
  • Thanks to the outer loop, this updating of the BasketItemAttributes field is done for each row in dTable.
  • Next, still in Cart.aspx.vb, add the following procedure to handle paging in the grid view when the user clicks on Previous or Next to see a different page of data:

Protected Sub ResultsGridView_PageIndexChanging(ByVal sender As Object, _
    ByVal e As GridViewPageEventArgs) Handles CartGridView.PageIndexChanging

    Dim dTable As DataTable = Nothing

    If Session.Count = 0 Then   ' No session items were supplied.
        ErrorLabel.Visible = True
    Else
        Try
            dTable = Session("DataTable")
            If dTable.Rows.Count = 0 Then
                ItemsLabel.Text = "0 items in cart"
                CartGridView.Visible = False
                SubTotalLabel.Visible = False
                BuyButton.Visible = False
                EmptyButton.Visible = False
            Else
                CartGridView.DataSource = dTable
                CartGridView.PageIndex = e.NewPageIndex  'Show new page
                CartGridView.DataBind()
                CartGridView.Visible = True
                SubTotalLabel.Visible = True
                BuyButton.Visible = True
                EmptyButton.Visible = True
                ' The info displayed on the labels should not need to be changed
            End If
        Catch exception As Exception
            ErrorLabel.Visible = True
        End Try
    End If
End Sub

  • Essentially this procedure gets the data in needs from what we already stored in session state. No stored procedure call to get data from the database is needed here.
  • Note the important line that sets the CurrentPageIndex.
  • Next, add the following procedure to handle a click on any of the Remove buttons that appear in each row of shopping cart data. Adjust the connection string line in the usual manner.

Protected Sub ResultsGridView_ItemCommand(ByVal Source As Object, _
    ByVal e As GridViewCommandEventArgs) Handles CartGridView.RowCommand

    Dim BasketItemNum As Integer
    Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
    Dim cmdRemoveBasketItem As System.Data.SqlClient.SqlCommand = Nothing
    Dim dTable As DataTable = Nothing

    If e.CommandName = "Remove" Then
        Try
            dTable = Session("DataTable")
            ' Find the index of the GridView row where the user clicked the button:
            Dim index As Integer = Convert.ToInt32(e.CommandArgument)

            Dim connectionString As String = _
                ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            cmdRemoveBasketItem = New System.Data.SqlClient.SqlCommand()

            ' Calculate the row of dTable where this basket item is, based on page we
            ' are on and the index within that page:
            BasketItemNum = dTable.Rows(CartGridView.PageSize * CartGridView.PageIndex _
                + index).ItemArray(5)
            cmdRemoveBasketItem.CommandText = "RemoveBasketItem"
            cmdRemoveBasketItem.CommandType = System.Data.CommandType.StoredProcedure
            cmdRemoveBasketItem.Connection = myDatabaseConnection

            cmdRemoveBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter( _
                "@RETURN_VALUE", System.Data.SqlDbType.Int, 4, _
                System.Data.ParameterDirection.ReturnValue, False, CType(10, Byte), _
                CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdRemoveBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter( _
                "@TargetBasketNum", System.Data.SqlDbType.Int, 4, _
                System.Data.ParameterDirection.Input, False, CType(10, Byte), _
                CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdRemoveBasketItem.Parameters.Add(New System.Data.SqlClient.SqlParameter( _
                "@TargetBasketItemNum", System.Data.SqlDbType.Int, 4, _
                System.Data.ParameterDirection.Input, False, CType(10, Byte), _
                CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))

            cmdRemoveBasketItem.Parameters("@TargetBasketNum").Value = Session("BasketNum")
            cmdRemoveBasketItem.Parameters("@TargetBasketItemNum").Value = BasketItemNum
            myDatabaseConnection.Open()
            cmdRemoveBasketItem.ExecuteNonQuery()
            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()
        Catch exception As System.Data.SqlClient.SqlException
            DatabaseErrorLabel.Visible = True
        Catch exception As Exception
            ErrorLabel.Visible = True
            ' ErrorLabel.Text = exception.Message  ' Uncomment this line to debug.
        Finally
            cmdRemoveBasketItem.Dispose()

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
            cmdRemoveBasketItem = Nothing
        End Try

        'Go back to first page as we may have removed last item on page already displayed.
        CartGridView.PageIndex = 0
        Session.Remove("DataTable")   ' Remove old DataTable as update will get a new one.
        UpdatePage()
    End If
End Sub

  • This procedure, too, looks up the data table that we saved in session state.
  • In this data table is the basket item number for the item to be removed. The trick is to figure out how to get it.
  • Since e.CommandArgument tells us the row number where the user clicked in the grid view (a number from 0 to 2, since there are up to 3 cars per page), we can add that to the page index times the number of cars per page (3 in this app, PageSize in general) to get the row number in dTable for the product to remove.
  • Column 5 of that row of dTable contains the desired basket item number.
  • This basket item number is passed as a parameter to a stored procedure, as is the basket number.
  • The stored procedure RemoveBasketItem is then executed to remove this row of data from the BasketItem table.
  • We then set the PageIndex for the grid view to 0, which indicates the first page. This is done in case the page we were on is now empty due to the removal of the only row on that page.
  • The UpdatePage procedure is then called to retrieve from BasketItems the updated shopping cart content and display it on the shopping cart page.
  • Create the RemoveBasketItem stored procedure so that it contains the following code:

CREATE PROCEDURE db_datareader.RemoveBasketItem 
    (
    @TargetBasketNum int,
    @TargetBasketItemNum int
    )
AS
    DELETE FROM BasketItem WHERE BasketNum = @TargetBasketNum
        AND BasketItemNum = @TargetBasketItemNum
    RETURN

  • Next we fill in the code for the procedure that handles a click on the Empty button.
  • The purpose of this procedure is to remove everything from the current shopping cart.
  • You can generate an outline of this procedure by double clicking on the Empty button.
  • Make your procedure look like the following, except that you must adjust the connection string line as usual.

Protected Sub EmptyButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles EmptyButton.Click

    Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
    Dim cmdEmptyBasket As System.Data.SqlClient.SqlCommand = Nothing

    Try
        Dim connectionString As String = _
            ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString
        myDatabaseConnection = New SqlClient.SqlConnection(connectionString)

        cmdEmptyBasket = New System.Data.SqlClient.SqlCommand()
        cmdEmptyBasket.CommandText = "EmptyBasket"
        cmdEmptyBasket.CommandType = System.Data.CommandType.StoredProcedure
        cmdEmptyBasket.Connection = myDatabaseConnection
        cmdEmptyBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter( _
            "@RETURN_VALUE", System.Data.SqlDbType.Int, 4, _
            System.Data.ParameterDirection.ReturnValue, False, CType(10, Byte), _
            CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
        cmdEmptyBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter( _
            "@TargetBasketNum", System.Data.SqlDbType.Int, 4, _
            System.Data.ParameterDirection.Input, False, CType(10, Byte), _
            CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
        cmdEmptyBasket.Parameters("@TargetBasketNum").Value = Session("BasketNum")

        myDatabaseConnection.Open()
        cmdEmptyBasket.ExecuteNonQuery()
        myDatabaseConnection.Close()
        myDatabaseConnection.Dispose()
    Catch exception As System.Data.SqlClient.SqlException
        DatabaseErrorLabel.Visible = True
    Catch exception As Exception
        ErrorLabel.Visible = True
    Finally
        cmdEmptyBasket.Dispose()

        If Not myDatabaseConnection Is Nothing AndAlso _
            myDatabaseConnection.State = ConnectionState.Open Then
            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()
        End If

        myDatabaseConnection = Nothing
        cmdEmptyBasket = Nothing
    End Try
    UpdatePage()
End Sub

  • The above procedure gets the basket number from the session state and passes it as a parameter to a stored procedure.
  • We then execute this stored procedure and update the data on the shopping cart page by calling the UpdatePage routine. (It will, of course, now show zero items in the cart.)
  • The needed stored procedure is called EmptyBasket and should be created as follows:

CREATE PROCEDURE db_datareader.EmptyBasket 
    (
    @TargetBasketNum int
    )
AS
    DELETE FROM BasketItem WHERE BasketNum = @TargetBasketNum 
    RETURN

  • Obviously this stored procedure deletes from the BasketItem table all rows that have the target basket number.
  • Finally, if your Cart.aspx.vb file does not have the outline of a function to handle a click on the BuyButton, double click on that button in Cart.aspx to create this outline.
  • Make this click handler look like the following so as to redirect the user to the shipping page when the Buy Items in Cart button is clicked.

Protected Sub BuyButton_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles BuyButton.Click

    Response.Redirect("Shipping.aspx")
End Sub

The Shipping Form

  • Shipping.aspx is pretty much ready to use.
  • However, the tab order for the controls should be adjusted to make things easier for the user. Change the TabIndex property for the controls down the middle of the form, from FirstNameBox to SubmitButton, to be the numbers from 1 to 9. This allows the user to quickly tab from one to the next.
  • The custom validator on StateList, the drop-down list, runs code on the server. This round trip to the server slows things down a little in the middle of data entry and interrupts the tabbing from one field to the next a bit.
  • Note that there is a minor mistake in the data inside StateList. The text for the states is the complete name of each, such as Pennsylvania, but New York is listed as just NY. Fix that by clicking on StateList's smart tag and selecting Edit Items. In the ListItem Collection Editor adjust the data as shown in this screen shot.

The Code-Behind File for the Shipping Page

  • In the Shipping.aspx.vb file, adjust the Page_Load procedure to look like this:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Me.Load

    If Not IsPostBack Then
        If CType(Session("BasketNum"), String) = "" Then   ' No shopping basket
            Response.Redirect("Cart.aspx")
        End If
    End If
End Sub

  • The above procedure simply checks to see if there is a basket number for this shopper. If not, it redirects the person to the shopping cart page (where they will see an empty shopping cart and hopefully realize that they need to add something to the cart before trying to go through the checkout process).
  • Then add the following procedure to handle the custom validator mentioned above for the state drop-down list:

Protected Sub CustomValidator1_ServerValidate(ByVal source As System.Object, _
    ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs) _
    Handles CustomValidator1.ServerValidate

    If StateList.SelectedItem.ToString = "Select a state" Then
        args.IsValid = False
    Else
        args.IsValid = True
    End If
End Sub

  • As you can see, the above code only validates the data if the user has selected something in the list other than "Select a state". In other words, the user is forced to select a state.
  • Note that the state list is incomplete. You can adding the missing items if you wish.
  • You probably already have the outline of the click handler for the submit button. If not, double click on the Submit button to get the outline. Make the code match the following:

Protected Sub SubmitButton_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles SubmitButton.Click

    'Page.Validate()is called automatically before any click handler.
    If Page.IsValid Then
        Session("FirstName") = FirstNameBox.Text
        Session("LastName") = LastNameBox.Text
        Session("Street") = StreetBox.Text
        Session("City") = CityBox.Text
        Session("State") = StateList.SelectedItem.Value
        Session("Zipcode") = ZipcodeBox.Text
        Session("Email") = EmailBox.Text
        Session("Phone") = PhoneBox.Text
        Response.Redirect("Payment.aspx")
    End If
End Sub

  • The above procedure only does something if the page's data validates.
  • If so, it saves the shipping information supplied by the user on this page. It does so by saving it in session state.
  • Then it redirects to the payment page.

The Code-Behind File for the Payment Page

  • The Payment.aspx page should be ready to use.
  • Although we only need to write code for the Page_Load procedure, it does call five stored procedures and is therefore lengthy.
  • Furthermore, these stored procedures will all be written and called using hand-written code; we will not use a data adapter or data reader.
  • This means using Parameters.Add to set up the parameters to our SqlClient.SqlCommand objects so that the names, types, and sizes match with what each stored procedure expects.
  • Adjust your Page_Load procedure to look like this, adjusting the connection string line as usual:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Me.Load

    Dim Quantity As Integer
    Dim Subtotal, Total, Tax, Shipping As Decimal
    Dim myDatabaseConnection As SqlClient.SqlConnection = Nothing
    Dim cmdBasketDetails As System.Data.SqlClient.SqlCommand = Nothing
    Dim cmdInsertOrder As SqlClient.SqlCommand = Nothing
    Dim cmdInsertPayment As SqlClient.SqlCommand = Nothing
    Dim cmdUpdateBasket As SqlClient.SqlCommand = Nothing
    Dim cmdUpdateShopper As SqlClient.SqlCommand = Nothing
    Dim connectionString As String = _
        ConfigurationManager.ConnectionStrings("PartsConnectionString").ConnectionString

    ErrorLabel.Visible = False

    If Not IsPostBack Then
        ' Make sure the user got here from a refresh of this page or from the shipping page:
        ' **** If you want to test your app locally, comment off this section until you
        ' **** have things working.  Then uncomment this section and publish your app to the
        ' **** live web server.  You MUST adjust the 2 URIs in this code to match your situtation.
        ' **** For example, the carlsond would need to be changed.
        Try
            ' Note: If you change to using https, adjust the 2 URIs here:
            If (LCase(Request.UrlReferrer.AbsoluteUri) <> _
                "http://cis2.stvincent.edu/carlsond/cars2/shipping.aspx") _
                AndAlso (LCase(Request.UrlReferrer.AbsoluteUri) <> _
                "http://cis2.stvincent.edu/carlsond/cars2/payment.aspx") Then
                Response.Redirect("Cart.aspx")
            End If
        Catch exception As Exception
            Response.Redirect("Cart.aspx")
        End Try
        ' **** End of section to comment off when testing locally.

        Try
            ' Get the Quantity and Subtotal from the database:
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            cmdBasketDetails = New System.Data.SqlClient.SqlCommand()
            cmdBasketDetails.CommandText = "BasketQuantitySubTotal"
            cmdBasketDetails.CommandType = System.Data.CommandType.StoredProcedure
            cmdBasketDetails.Connection = myDatabaseConnection
            cmdBasketDetails.Parameters.Add(New System.Data.SqlClient.SqlParameter("@RETURN_VALUE", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.ReturnValue, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdBasketDetails.Parameters.Add(New System.Data.SqlClient.SqlParameter("@TargetBasketNum", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdBasketDetails.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Quantity", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Output, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdBasketDetails.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Subtotal", _
                System.Data.SqlDbType.Money, 8, System.Data.ParameterDirection.Output, False, _
                CType(19, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdBasketDetails.Parameters("@TargetBasketNum").Value = Session("BasketNum")

            myDatabaseConnection.Open()
            cmdBasketDetails.ExecuteNonQuery()
            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()

            Subtotal = cmdBasketDetails.Parameters("@Subtotal").Value
            Quantity = cmdBasketDetails.Parameters("@Quantity").Value
            Session("Subtotal") = Subtotal
            Session("Quantity") = Quantity
        Catch exception As Exception
            ErrorLabel.Visible = True
        Finally
            cmdBasketDetails.Dispose()

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
            cmdBasketDetails = Nothing
        End Try

        If Quantity = 0 Then   ' There is nothing to purchase.
            Response.Redirect("Cart.aspx")
        End If

        Shipping = Quantity * 125  ' Assumes a $125 shipping fee.  Adjust as needed.
        Tax = 0.06 * Subtotal  ' Assumes a 6% sales tax.  Adjust this as needed.
        Total = Subtotal + Tax + Shipping
        Session("Shipping") = Shipping
        Session("Tax") = Tax
        Session("Total") = Total

        SubtotalLabel.Text = Format(Subtotal, "Currency")
        ShippingLabel.Text = Format(Shipping, "Currency")
        TaxLabel.Text = Format(Tax, "Currency")
        TotalLabel.Text = Format(Total, "Currency")
    Else   ' Postback case
        ' Save the information that's on the page into session state.
        ' Also, remove any - characters from the credit card number.
        Session("NameOnCard") = NameOnCardBox.Text
        Session("CardNumber") = Replace(CardNumberBox.Text, "-", "")  ' Drop - characters.
        Session("CardType") = CardTypeList.SelectedItem.Value
        Session("CardExpireDate") = CardExpireMonthList.SelectedItem.Value & "/" & _
            CardExpireYearList.SelectedItem.Value

        ' Here is where we would likely check to see if the credit card payment is accepted or not.
        ' If not, the user would be redirected to a page explaining that the purchase was denied.
        ' Perhaps an email explaining the denial would also be sent.
        ' Because of the redirection to the deny page, the data for this intended purchase does
        ' not get written to the Order, Payment, and Shopper tables, not does the corresponding row
        ' of the Basket table have the BasketOrderPlaced field value changed from 0 to 1.
        ' Thus the order will not get processed.  Most of the data for it is discarded, though
        ' the product details are still in the BasketItemTable.

        ' The credit card check is omitted here on our first pass at this procedure.
        ' It might well take the form of a call to a web service.

        'Use InsertOrder stored procedure to insert relevant data as a new row in Order table:
        Try
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            cmdInsertOrder = New System.Data.SqlClient.SqlCommand()
            cmdInsertOrder.CommandText = "InsertOrder"
            cmdInsertOrder.CommandType = System.Data.CommandType.StoredProcedure
            cmdInsertOrder.Connection = myDatabaseConnection
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@RETURN_VALUE", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.ReturnValue, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@ShopperNum", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@BasketNum", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@FirstName", _
                System.Data.SqlDbType.VarChar, 24))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@LastName", _
                System.Data.SqlDbType.VarChar, 32))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Street", _
                System.Data.SqlDbType.VarChar, 32))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@City", _
                System.Data.SqlDbType.VarChar, 32))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@State", _
                System.Data.SqlDbType.VarChar, 2))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@ZipCode", _
                System.Data.SqlDbType.VarChar, 15))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Phone", _
                System.Data.SqlDbType.VarChar, 30))
            cmdInsertOrder.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Email", _
                System.Data.SqlDbType.VarChar, 48))

            cmdInsertOrder.Parameters("@ShopperNum").Value = Session("ShopperNum")
            cmdInsertOrder.Parameters("@BasketNum").Value = Session("BasketNum")
            cmdInsertOrder.Parameters("@FirstName").Value = Session("FirstName")
            cmdInsertOrder.Parameters("@LastName").Value = Session("LastName")
            cmdInsertOrder.Parameters("@Street").Value = Session("Street")
            cmdInsertOrder.Parameters("@City").Value = Session("City")
            cmdInsertOrder.Parameters("@State").Value = Session("State")
            cmdInsertOrder.Parameters("@ZipCode").Value = Session("Zipcode")
            cmdInsertOrder.Parameters("@Phone").Value = Session("Phone")
            cmdInsertOrder.Parameters("@Email").Value = Session("Email")

            myDatabaseConnection.Open()
            Session("OrderNum") = cmdInsertOrder.ExecuteScalar
            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()
        Catch exception As Exception
            ErrorLabel.Visible = True
        Finally
            cmdInsertOrder.Dispose()

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
            cmdInsertOrder = Nothing
        End Try

        'Use InsertPayment stored procedure to insert relevant data as a new row in Payment table:
        Try
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            cmdInsertPayment = New System.Data.SqlClient.SqlCommand()
            cmdInsertPayment.CommandText = "InsertPayment"
            cmdInsertPayment.CommandType = System.Data.CommandType.StoredProcedure
            cmdInsertPayment.Connection = myDatabaseConnection
            cmdInsertPayment.Parameters.Add(New System.Data.SqlClient.SqlParameter("@RETURN_VALUE", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.ReturnValue, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdInsertPayment.Parameters.Add(New System.Data.SqlClient.SqlParameter("@OrderNum", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdInsertPayment.Parameters.Add(New System.Data.SqlClient.SqlParameter("@CardType", _
                System.Data.SqlDbType.VarChar, 16))
            cmdInsertPayment.Parameters.Add(New System.Data.SqlClient.SqlParameter("@CardNumber", _
                System.Data.SqlDbType.VarChar, 30))
            cmdInsertPayment.Parameters.Add(New System.Data.SqlClient.SqlParameter("@CardExpireDate", _
                System.Data.SqlDbType.VarChar, 24))
            cmdInsertPayment.Parameters.Add(New System.Data.SqlClient.SqlParameter("@NameOnCard", _
                System.Data.SqlDbType.VarChar, 64))

            cmdInsertPayment.Parameters("@OrderNum").Value = Session("OrderNum")
            cmdInsertPayment.Parameters("@CardType").Value = Session("CardType")
            cmdInsertPayment.Parameters("@CardNumber").Value = Session("CardNumber")
            cmdInsertPayment.Parameters("@CardExpireDate").Value = Session("CardExpireDate")
            cmdInsertPayment.Parameters("@NameOnCard").Value = Session("NameOnCard")

            myDatabaseConnection.Open()
            cmdInsertPayment.ExecuteNonQuery()
            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()
        Catch exception As Exception
            ErrorLabel.Visible = True
        Finally
            cmdInsertPayment.Dispose()

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
            cmdInsertPayment = Nothing
        End Try

        'Use UpdateBasket stored procedure to update relevant data in Basket table:
        Try
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            cmdUpdateBasket = New System.Data.SqlClient.SqlCommand()
            cmdUpdateBasket.CommandText = "UpdateBasket"
            cmdUpdateBasket.CommandType = System.Data.CommandType.StoredProcedure
            cmdUpdateBasket.Connection = myDatabaseConnection
            cmdUpdateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@RETURN_VALUE", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.ReturnValue, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdUpdateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@BasketNum", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdUpdateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@BasketOrderPlaced", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdUpdateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Quantity", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdUpdateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Subtotal", _
                System.Data.SqlDbType.Money, 8, System.Data.ParameterDirection.Input, False, _
                CType(19, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdUpdateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Total", _
                System.Data.SqlDbType.Money, 8, System.Data.ParameterDirection.Input, False, _
                CType(19, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdUpdateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Shipping", _
                System.Data.SqlDbType.Money, 8, System.Data.ParameterDirection.Input, False, _
                CType(19, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdUpdateBasket.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Tax", _
                System.Data.SqlDbType.Money, 8, System.Data.ParameterDirection.Input, False, _
                CType(19, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))

            cmdUpdateBasket.Parameters("@BasketNum").Value = Session("BasketNum")
            cmdUpdateBasket.Parameters("@BasketOrderPlaced").Value = 1  'meaning true
            cmdUpdateBasket.Parameters("@Quantity").Value = Session("Quantity")
            cmdUpdateBasket.Parameters("@Subtotal").Value = Session("Subtotal")
            cmdUpdateBasket.Parameters("@Total").Value = Session("Total")
            cmdUpdateBasket.Parameters("@Shipping").Value = Session("Shipping")
            cmdUpdateBasket.Parameters("@Tax").Value = Session("Tax")

            myDatabaseConnection.Open()
            cmdUpdateBasket.ExecuteNonQuery()
            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()
        Catch exception As Exception
            ErrorLabel.Visible = True
        Finally
            cmdUpdateBasket.Dispose()

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
            cmdUpdateBasket = Nothing
        End Try

        'Use UpdateShopper stored procedure to update relevant data in Shopper table.
        'We could later use this table in the management app to send promotions to customers.
        'We could also use it to implement customer profiles so that returning customers
        'would not have to enter their address information.
        Try
            myDatabaseConnection = New SqlClient.SqlConnection(connectionString)
            cmdUpdateShopper = New System.Data.SqlClient.SqlCommand()
            cmdUpdateShopper.CommandText = "UpdateShopper"
            cmdUpdateShopper.CommandType = System.Data.CommandType.StoredProcedure
            cmdUpdateShopper.Connection = myDatabaseConnection
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@RETURN_VALUE", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.ReturnValue, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@ShopperNum", _
                System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, False, _
                CType(10, Byte), CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@FirstName", _
                System.Data.SqlDbType.VarChar, 24))
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@LastName", _
                System.Data.SqlDbType.VarChar, 32))
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Street", _
                System.Data.SqlDbType.VarChar, 32))
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@City", _
                System.Data.SqlDbType.VarChar, 32))
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@State", _
                System.Data.SqlDbType.VarChar, 2))
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@ZipCode", _
                System.Data.SqlDbType.VarChar, 15))
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Phone", _
                System.Data.SqlDbType.VarChar, 30))
            cmdUpdateShopper.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Email", _
                System.Data.SqlDbType.VarChar, 48))

            cmdUpdateShopper.Parameters("@ShopperNum").Value = Session("ShopperNum")
            cmdUpdateShopper.Parameters("@FirstName").Value = Session("FirstName")
            cmdUpdateShopper.Parameters("@LastName").Value = Session("LastName")
            cmdUpdateShopper.Parameters("@Street").Value = Session("Street")
            cmdUpdateShopper.Parameters("@City").Value = Session("City")
            cmdUpdateShopper.Parameters("@State").Value = Session("State")
            cmdUpdateShopper.Parameters("@ZipCode").Value = Session("Zipcode")
            cmdUpdateShopper.Parameters("@Phone").Value = Session("Phone")
            cmdUpdateShopper.Parameters("@Email").Value = Session("Email")
            myDatabaseConnection.Open()
            cmdUpdateShopper.ExecuteNonQuery()
            myDatabaseConnection.Close()
            myDatabaseConnection.Dispose()
        Catch exception As Exception
            ErrorLabel.Visible = True
        Finally
            cmdUpdateShopper.Dispose()

            If Not myDatabaseConnection Is Nothing AndAlso _
                myDatabaseConnection.State = ConnectionState.Open Then
                myDatabaseConnection.Close()
                myDatabaseConnection.Dispose()
            End If

            myDatabaseConnection = Nothing
            cmdUpdateShopper = Nothing
        End Try

        'The next step is typically to send an email confirmation to the customer
        'and to present a page confirming the order:
        Response.Redirect("Confirm.aspx")
    End If
End Sub

  • The Page_Load procedure begins by handling the case when we do not have a postback, that is, when the page first loads.
  • The first Try...Catch compares Request.UrlReferrer.AbsoluteUri with the Uri for the shipment.aspx and payment.aspx pages. If the referrer, the page that the user came from to get to the payment page, is not the shipment page or the payment page (due to a refresh), then it does not make sense for the user to be on this page. (Perhaps the user followed a bookmark directly to the payment page.) Therefore, the user is redirected to the shopping cart page.
  • If for some reason the code cannot evaluate Request.UrlReferrer.AbsoluteUri, the Catch clause redirects the user to the shopping cart page.
  • The next Try...Catch is used to call the BasketQuantitySubTotal stored procedure, a stored procedure that we wrote previously, so as to retrieve from the database the subtotal for this order and the number of items in the order.
  • To call the BasketQuantitySubTotal stored procedure we pass it the basket number and it returns the 2 values that we want in the parameters Subtotal and Quantity.
  • We store these 2 values in the session state.
  • If the Quanity value (number of items in the order) is zero, then there is nothing to purchase, so we redirect the user to the shopping cart page.
  • We next calculate the amount of tax and shipping with some simple calculations. For a real e-commerce app these would be more complex, taking into account the tax laws of each state. Also, since these calculations might need to be changed it might make sense to call a web service to do the calculations. That way when the tax or shipping calculations need to be changed, only the web service needs to be modified. The e-commerce app itself would not have to be changed.
  • The shipping, tax, and total amounts are stored in the session state.
  • We also display the subtotal along with these 3 new monetary values in the appropriate four labels.
  • That takes care of what Page_Load does in the non-postback case. What the user sees on the screen in the normal case is the four monetary values just discussed.
  • Next, the Page_Load procedure handles the postback case. This would occur after the user fills in the credit card information needed on this page and clicks the Submit button. Of course, the user might fail to fill in some or all of the data and still click the submit button. That is why we put required field validators on this page.
  • The primary task in this section of code, then, is to call some stored procedures to write the user's data, both from this page and previous ones, to the database. This is where the user gives final assent to the order and the remaining data is committed to the database.
  • Before writing any data to the database we should fix it up a bit. Any - characters in the the credit card number are removed so that all credit card numbers get stored as a string of digits only. The modified data is saved in session state.
  • This is also the spot, as indicated by the comments in the code, where we could call a web service to validate this credit card purchase. If the purchase is rejected, the user would be redirected to a deny page, without the key purchase data being written to the database. We will consider this web service idea further below.
  • If the purchase is accepted, we next use the InsertOrder stored procedure to write a row of data to the Order table. The code for this stored procedure is shown in the next box below. We pass as parameters the values that we want placed in this new row, namely the shopper number, basket number for this order, first name and last name of the shopper, as well as the shopper's address and other contact information. Since OrderNum is an indentity field in the table, the order number is created automatically. Our stored procedure returns it when the stored procedure is called by ExecuteScalar and our VB code saves it in session state.

CREATE PROCEDURE db_datareader.InsertOrder 
    (
    @ShopperNum int,
    @BasketNum int,
    @FirstName varchar(24),
    @LastName varchar(32),
    @Street varchar(32),
    @City varchar(32),
    @State varchar(2),
    @ZipCode varchar(15),
    @Phone varchar(30),
    @Email varchar(48)
    )
AS
    INSERT INTO [Order]
    (ShopperNum, BasketNum, OrderFirstName, OrderLastName, OrderStreet, OrderCity, 
        OrderState, OrderZipCode, OrderPhone, OrderEmail, OrderDateOrdered)
    VALUES
    (@ShopperNum, @BasketNum, @FirstName, @LastName, @Street, @City, @State,
        @ZipCode, @Phone, @Email, GETDATE())
    SELECT @@IDENTITY 
    RETURN

  • The only other thing to note about this stored procedure is that it uses the GETDATE() function in SQL Server to look up the system time and date. This value is put into the OrderDateOrdered field by the stored procedure.
  • Our code next uses the InsertPayment stored procedure to write the credit card information and order number to the Payment table. The code for InsertPayment is shown below. Note how our VB code appends the credit card's month of expiration with the expiration year (and a / character in between). We then use this combined date as the value to pass in the @CardExpireDate parameter.

CREATE PROCEDURE db_datareader.InsertPayment 
    (
    @OrderNum int,
    @CardType varchar(16),
    @CardNumber varchar(30),
    @CardExpireDate varchar(24),
    @NameOnCard varchar(64)
    )
AS
    INSERT INTO Payment (OrderNum, PaymentCardType, PaymentCardNumber,
        PaymentExpirationDate, PaymentCardOwnerName)
    VALUES (@OrderNum, @CardType, @CardNumber, @CardExpireDate, @NameOnCard) 
    RETURN

  • Next we have the UpdateBasket stored procedure update the row of the Basket table for the shopping basket being purchased (the one with BasketNum as its ID number).
  • Remember that up until this point all we have in this row of data is the automatically-generated basket number and the shopper number.
  • Our stored procedure, shown below, thus finds the row with the desired basket number (as passed in via the @BasketNum parameter). It then fills in the values from the other parameters: the quantity (number of items in the basket), the subtotal, the shipping, the tax, and the total amount of the order. Notice that 1 (true) is passed in as the value that gets put into the BasketOrderPlaced field, thus indicating that the order has indeed now been placed. Our management app (not shown) might then be used to actually ship the order.

CREATE PROCEDURE db_datareader.UpdateBasket 
    (
    @BasketNum int,
    @BasketOrderPlaced int,
    @Quantity int,
    @Subtotal money,
    @Total money,
    @Shipping money,
    @Tax money
    )
AS
    UPDATE Basket
    SET BasketNumItems = @Quantity,
    BasketOrderPlaced = @BasketOrderPlaced,
    BasketSubtotal = @Subtotal,
    BasketTotal = @Total,
    BasketShipping = @Shipping,
    BasketTax = @Tax
    /* ShopperNum does not change */
    WHERE
    BasketNum = @BasketNum 
    RETURN

  • Page_Load then calls its last stored procedure, UpdateShopper.
  • You may recall that up until this point there is nothing else in the Shopper table's row of data about the current shopper than the shopper number.
  • This stored procedure, as seen below, fills in the shopper's first and last name, address, phone, and email address.

CREATE PROCEDURE db_datareader.UpdateShopper 
    (
    @ShopperNum int,
    @FirstName varchar(24),
    @LastName varchar(32),
    @Street varchar(32),
    @City varchar(32),
    @State varchar(2),
    @ZipCode varchar(15),
    @Phone varchar(30),
    @Email varchar(48)
    )
AS
    UPDATE Shopper
    SET ShopperFirstName = @FirstName,
    ShopperLastName = @LastName,
    ShopperStreet = @Street,
    ShopperCity = @City,
    ShopperState = @State,
    ShopperZipCode = @ZipCode,
    ShopperPhone = @Phone,
    ShopperEmail = @Email
    WHERE
    ShopperNum = @ShopperNum 
    RETURN

  • As mentioned before, we intend to keep this shopper information long-term, both to send promotional information (advertisements) to the shoppers and to enable us to implement shopper profiles if we wish. (The latter would, when the correct password is given, supply to a previous shopper his or her old shopper number and contact information.
  • In contrast, the Payment table of credit card information is purged quickly so that such sensitive information does not easily fall prey to thieves. Similarly, the Order table of data about particular orders is probably cleared periodically of old data.
  • Our Page_Load procedure ends by redirecting to an order confirmation page.

The Code-Behind File for the Confirmation Page

  • The Confirm.aspx page should be fine as is.
  • There is only a little that needs to be done in the Confirm.aspx.vb code-behind file.
  • Adjust the Page_Load procedure to match that shown below.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Me.Load

    Dim OrderNum As String

    OrderNum = CType(Session("OrderNum"), String)
    If OrderNum = "" Then   'No order number, so makes no sense to be on this page.
        Response.Redirect("Cart.aspx")
    End If

    'Here we would probably send an email to the customer confirming the order.
    'We keep the example shorter by skipping that and just show the confirmation page.
    OrderNumLabel.Text = OrderNum
    Session.Abandon() 'Clear out all session information now that we are done with it.
End Sub

  • As you can see, this Page_Load procedure looks up the order number in session state.
  • If no order number is found, the user is redirected to the shopping cart page as the person does not belong on the confirmation page if no order has been placed.
  • If there is an order number, it is displayed on the confirmation page along with the usual static text.
  • Lastly, we clear out all of the session state information. That ends this shopping session. There is no sense in keeping this information in session state as it would likely confuse things if the shopper went to the brands or search page and started more shopping.
  • There is one more thing to add: the credit card check and the deny page, but we do that after checking that what we have thus far works properly.

Finishing Off the Web App



Getting the App Running and Checking the Tables

  • Once you have your web app completed, try it out locally. (Be sure the section of code in Payment.aspx.vb using Request.UrlReferrer.AbsoluteUri is commented off as previously suggested. Otherwise, it will interfere with running your app locally.)
  • Use Start Without Debugging (or even Start With Debugging if you are having problems).
  • Try submitting orders for a few shopping carts in the name of various people. (Just make up the data. For sure, don't use real credit card numbers!)
  • If all seems to work in the web interface, use Server Explorer to look at the tables to see that they have the correct data. The key tables to examine are Basket, BasketItem, Order, Payment, and Shopper.
  • You should be able to find exactly the data for what you did in the web interface.
  • Pay particular attention to removing an item from the cart and emptying the cart to see that they work, especially in the complicated case of having several pages of items in the cart.
  • If you get an error, you might try customErrors mode="Off" in your Web.config file so as to be able to see the details of the error.
  • If an exception is being thrown and indicated by a red error label showing up on one of your forms, you need a way to see more details about the exception. As sometimes indicated in comments in the VB code above, you can assign exception.Message into the Text field of a label on your form so as to see this information. Just be sure that the label's Visible property is True and that the page doesn't redirect to another one before you can see the information.
  • Once things are working fine, uncomment the section in Payment.aspx.vb using Request.UrlReferrer.AbsoluteUri, adjust the 2 URIs to where you plan to post your pages, and build and publish your app to that location. Get your web administrator to make the published copy an official IIS web app, and then test it in a browser by going to the correct web address. (For example, http://cis2.stvincent.edu/carlsond/cars2/home.aspx or similar.)
  • Make sure that if you try to go directly to the payment page, as in http://cis2.stvincent.edu/carlsond/cars2/payment.aspx, you get redirected to the cart page.

Credit Card Processing and Deny.aspx Page

  • In Payment.aspx.vb, add the code needed to call a simple web service to simulate the processing of the credit card data. You might check the Deitel and Deitel book Visual Basic 2005: How to Program, 3rd ed., chapter 22.
  • In CS 305 at Saint Vincent College, just use the demo web service that has been set up for you. See the course web page for information on this.
  • Other readers might want to set up their own demo web service with a CreditCardApproval function to process the credit card data. This data should probably include the purchase amount, the credit card type, the credit card number, the card's expiration date, and the name on the card. You might set it up to accept only one credit card type and reject all others, to use a random acceptance, or to actually use a table of information on (pretend) credit card customers and their available credit. The Deitel and Deitel book just mentioned would be helpful in creating the web service.
  • In any case, you need a new Web Content Form (not a regular web form), named Deny.aspx, in your project. Create it and add some appropriate message on it to explain to the user that the intended purchase was rejected.
  • The Deny.aspx.vb file's Page_Load routine should simply use Session.Abandon() to remove all of the information on the intended purchase from session state.
  • Test your web app to be sure that intended purchases get accepted or rejected as expected.
  • If you are curious about real credit card processing, check out TopTenReviews and Verifone.

Extra Credit Features

  • If you have any time and energy at this point, you might consider adding some extra credit features to this ecommerce application.
  • This could include anything that seems to be missing but useful for such an ecommerce application. Some possibilities include the following:
    • Force the user to start at a login page and to login with a username and password before getting to the ecommerce pages shown above. You already know the basics of doing this from the earlier web app Online Survey Requiring Login. You would want to add a logout button to the list of navigation buttons that show up on all of the pages.
    • The user should be forced to use https so as to provide encryption of data and identification of the web site via a certificate. This is just a quick web server setting.
    • Allowing separate shopping and billing addresses, along with the ability to copy one into the other at the click of a button, would be a nice feature.
    • Implement user profiles, as mentioned above, to allow users to store their contact information in the Shopper table and have it automatically retrieved on return shopping trips when they log in.
    • If your server allows apps to send email, add the sending of an email message to the user when the Confirm.aspx or Deny.aspx page is reached. (In CS 305 at Saint Vincent, our server does not allow email.) You will have to look up what code to use for this.
    • A web service could be used to calculate the amount of tax on the order, based on the applicable tax laws.
    • Create the management application previously hinted at. It should allow the management team to adjust the data in the tables, perhaps when fulfilling orders, adding new products to the site, clearing out stale data, etc. This would be a large project. It might be a Windows app or a web app, though security would be especially important if a web app is used. Chapters 10, 11, and 12 of the Gunderloy and Jerke book previously mentioned show in detail how to write such a management app.

The Web Configuration File

  • Once you are sure that your e-commerce app is working correctly, you can make some final changes to your Web.config file.
  • Set debug="false" as that will leave out debugging symbols, thus producing a smaller, faster app.
  • Delete customErrors mode="Off" if you used it to see what error messages your app was generating. You don't want attackers to be able to see these error messages if they find a way to force your app to produce an error.
  • Build and publish your web app again now that these final changes have been made.

Concluding Remarks

  • You have now completed a functional e-commerce app.
  • However, we did not write the separate management app that would allow a manager to check on things, fulfill orders, make changes to the database, etc.
  • We also only hinted at a number of extra credit features that could (or should) be added to this app.

Back to the main page for ASP .NET Web Apps



Author: Br. David Carlson
Last updated: November 23, 2008
Disclaimer