CIS Logo SVC Logo

   Computing & Information Systems


Schoology Facebook        Search CIS Site      Tutorials

Car Store 2

The Goals for this Example

This example simply expands on the previous car store app. Thus you should work on a copy of that web site. The main goal is to add the shopping cart, but the checkout process is not implemented in this example.

Database Work

The directions below assume that you use SQL Server Management Studio to do all of the database work that is described.

CarProduct Table

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

CarCategory Table

  • This table, too, should work unchanged.

CarCategoryProduct Table

  • This table will work unchanged unless you added new car data to the CarProduct table.
  • If you did add new data to the CarProduct 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.)

CarBasket Table

  • This table is used to hold general information about the shopping basket (cart).
  • A separate BasketItem table will hold information about each item in the shopping basket.
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.
  • The idea for the BasketOrderPlaced field value is that it 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 CarBasket. Do NOT save it until you have set up the primary key, the identity field and the default values.
  • No data is added to the Basket table ahead of time. Our e-commerce app will add data when users create shopping baskets.

CarBasketItem Table

  • This table is used to hold information about an individual type of car in the shopping basket.
  • Note that a user is able to purchase 1 or more cars of the same type. The BasketItemQuantity field will show the number of these identical cars.
  • Also note that what you might call the BasketItem number uses the simpler field name of ID.
  • Design the table as shown below.
Column Name Data Type Allow Nulls
ID int no
BasketNum int no
ProdNum int no
BasketItemPrice money no
BasketItemProdName varchar(24) no
BasketItemQuantity int no
BasketItemAttributes varchar(64) yes
BasketItemTotal money no

  • Make ID the primary key.
  • Also make it an identity field.
  • Give a default value of 0 to the BasketItemPrice, BasketItemQuantity, and BasketItemTotal fields. You find this as a property in the Column Properties box below the table designer.
  • Save this table under the name CarBasketItem.
  • No data is added to this table ahead of time. Our e-commerce app will add data when users add products to their shopping baskets.
  • As sample data you might see, for example, that basket 4 has basket items 6 and 7, which hold 1 Kangaroo car and 3 Tornados, respectively.

CarShopper Table

  • This table is used to hold the name and contact information of 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.
  • Design the table as follows:
Column Name Data Type Allow Nulls
ID int no
ShopperFirstName varchar(24) yes
ShopperLastName varchar(32) no
ShopperStreet varchar(32) no
ShopperCity varchar(32) no
ShopperState varchar(2) no
ShopperZipCode varchar(15) no
ShopperPhone varchar(30) yes
ShopperEmail varchar(48) no

  • Make ID (which is essentially the shopper's ID number) the primary key.
  • Also make it an identity field.
  • Save this table under the name CarShopper.
  • No data is added to this table ahead of time. Our e-commerce app will add data when a shopper checks out.

CarOrder 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
ID int no
ShopperNum int no
BasketNum int no
OrderFirstName varchar(24) yes
OrderLastName varchar(32) no
OrderStreet varchar(32) no
OrderCity varchar(32) no
OrderState varchar(2) no
OrderZipCode varchar(15) no
OrderPhone varchar(30) yes
OrderEmail varchar(48) no
OrderDateOrdered datetime no

  • Make ID the primary key.
  • Also make it an identity field.
  • 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 design 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_CarOrder. If you check its Columns property, you will see that ID is listed there. That says that ID 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 this table under the name CarOrder.
  • 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).

CarPayment 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. In fact, the credit card number should be stored in encrypted form, even if it is only kept for a short time. We will skip that in order to keep this example from being too lengthy.
  • Design the table as follows:
Column Name Data Type Allow Nulls
ID int no
OrderNum int no
PaymentCardType varchar(16) no
PaymentCardNumber varchar(30) no
PaymentExpirationDate varchar(24) no
PaymentCardOwnerName varchar(64) no

  • Make ID the primary key.
  • Also make it an identity field.
  • 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.
  • Save this table under the name CarPayment.
  • 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 store1 car 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 CarBasketItem table contains BasketNum as a foreign key since BasketNum is the primary key in the CarBasket table. The following are the details on how to set up this relationship.
      • Begin by opening table design for the table containing the foreign key. Thus, in this case, you would right click on the CarBasketItem table and select Design.
      • 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 CarBasket 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 nothing. That will clear out the unwanted value.
      • The righthand column should already have the correct foreign key table shown, namely the CarBasketItem table. The field shown under it should be the BasketNum field, the one that matches with the left column. Both field choices should be on the same line.
      • Check this picture of how the dialog box should now look. Click OK when you have this set up correctly.
      • Note that for me and some others, this relationship later interfered with the code that handles the Add to Cart button, so that I ended up deleting it and rebuilding my entity framework model. However, you may want to try leaving this in to see if you can get your code to work with it. If you do omit this relationship, once your project works, you could add the relationship back in, as described above, save what you have done in SQL Server Management Studio, and then in Visual Studio, delete all the files in the App_Code folder and build a new model based on your updated database. This has worked for me and others. It might also work to keep your App_Code files, right click on the screen where Visual Studio displays your model, and get it to update the model from the database.
    • The CarBasketItem table also contains foreign key ProdNum since it matches with ID, the primary key in the CarProduct table. Check this screen shot showing this relationship. When finished with the CarBasketItem table, be sure to save it.
    • Next, open the CarBasket table. It contains foreign key ShopperNum since it matches with ID, the primary key in the CarShopper table. When finished with the CarBasket table, be sure to save it.
    • Now open the CarOrder table. It contains ShopperNum as a foreign key since ShopperNum matches with ID, the primary key in the CarShopper table.
    • The CarOrder table also contains BasketNum as a foreign key since BasketNum is the primary key in the CarBasket table. When finished with the CarOrder table, be sure to save it.
    • Open the CarPayment table. It contains foreign key OrderNum since OrderNum matches with ID, the primary key in the CarOrder table. When finished with the CarPayment table, be sure to save it.

Adding and Adjusting Forms

New or Revised Forms

  • First, make sure that you have made a copy of your Store1 website folder and named the copy Store2. Have this on your W drive so that it can be accessed by the live web server.
  • The new files and revised ones can be obtained from
  • Files such as Cart.aspx that you already had in Store1 should replace your old ones.
  • If, in Store1, you figured out some useful VB code, you probably want to check if you can fill it in at the same places in the new files.
  • Note that the new aspx.vb files do have a few blanks for you to fill in with the correct code. These lines are usually commented off to try to keep syntax error messages to a minimum. Once you get the code filled in, uncomment these lines.
  • The .aspx files should be whole and complete.

The Results Page

  • As we left this page in store 1, it simply displayed the information on a particular car looked up by brand and model or with the search page and then clicking on the View button.
  • Now we need to make the "Add to Cart" button work.
  • There is nothing unusual on the Results.aspx form itself. You can look up the details of each item under properties, or, for the dropdown list, under its smart tag, Edit Items.
  • In Results.aspx.vb, the Page_Load routine needs to do two main tasks: Make sure that the user has a shopper number and display the data about the selected car on the form. Here are some of the details:
    • If there is no shopper number in Session state that means that this is the first time the user has reached this page during the user's current browser session.
    • On the other hand, it would be awkward to ask the user for first name, last name, address, etc. at this point in order to create a new, complete CarShopper table entry in the database.
    • Instead, a new CarShopper object is created with empty strings for most of its data. The last name is filled in with the current system time (in ticks, a very small unit of time). This is just a temporary and (probably) unique name to be used until the user goes to check out and fills in the name and address data.
    • This new CarShopper object is added to the entity collection and saved to the database.
    • We then need to know the ID number for the record just added to the CarShopper table. This is the shopper number.
    • One way, the method shown here, to get this ID number is to do a LINQ query to obtain the CarShopper object that has the saved current time as the last name. We then grab the ID number from this object.
    • The rest of Page_Load does a LINQ query to get the car data for the car whose brand and model are currently saved in Session state. This information, as well as the brand and model, are then displayed on the form in the text box and various labels. The car data is also saved to Session state in case we need it later.
  • Also in Results.aspx.vb, we have the AddToCartButton_Click routine. It does the following:
    • It gets the quantity desired from the dropdown list and saves it in Session state.
    • If there is no shopping basket number, that means that the user has not previously clicking this button during the current session. (Note that we use a shopping cart theme on the store web pages, but internally in the code we have a shopping basket theme. You might prefer to consistently use one theme.)
    • Checking to see if Session("BasketNum") is the empty string, though it sounds reasonable, does not work. You have to use the Is Nothing test.
    • So, if a shopping basket number is needed, we make a new basket object, with just the shopper number filled in. Other data will get added later. Of course, the database will automatically supply a new basket number, since that is an identity field. Notice how we obtain that basket number: the basket object that we just saved to the database now automatically contains the basket number that the database supplied. The basket number is saved to Session state so that we can easily access it again.
    • Next, the routine retrieves several items from Session state and calculates the total cost for the items in the current car and quantity (as retrieved from Session state).
    • The next task is to get this new desired purchase into the database.
    • But we need to know if the same shopper already has some quantity of this car in the shopping basket or if this would be a new item for this shopping basket.
    • A LINQ query is used to find all basket items that have the same product number and basket number as the new desired purchase.
    • If there are no such basket items, a new CarBasketItem object is made and saved to the database.
    • If there is such a basket item, it would be the only one (because our web app doesn't allow the same car to be put into two different basket items). In this case, we just update the existing basket item to reflect the shopper's wish to purchase more of the same item.

The Cart Page

  • In store 1, Cart.aspx was an empty form. Now, it contains a lot of controls. There are labels, a grid view, and buttons, some of which are within the grid view and some outside it.
  • You can examine the properties of any of these, but the most complex and interesting one is the grid view.
  • Under Properties, the grid view has AllowPaging set to True, AutoGenerateColumns is set to false (as the columns are manually configured), certain colors are selected (including under the EmptyDataRow style), PagerSettings has the PageButtonCount set at 3 (the maximum number of buttons or links to pages at the bottom of the grid view when not all of the data fits on one page), and Visible is set to False initially.
  • Under the grid view's smart tag, Edit Columns shows how the columns were set up. All are bound fields except for Remove, which is a command field. For each bound field, HeaderText gives the text at the top of the column and DataField indicates the column (field) name for the data to be placed in this column. In other words, the data coming from the database (via the Entity Framework in our case) must have the exact field name listed as the DataField for the data to be placed in this particular column of the grid view.
  • The command field called Remove has several properties that were set up for you: the button type is button (instead of link or image), DeleteText (the text on the button) is Remove, HeaderText (the text for the top of the column) is also Remove, and under the Behavior section, ShowDeleteButton and ShowHeader are both True.
  • In Cart.aspx.vb there is an UpdatePage routine. Its main task is to display the basket items in the grid view. This routine is private and is used by some of the other routines in Cart.aspx.vb at the point where they need to update the data on the Cart.aspx form.
    • A LINQ query is used to retrieve all of the basket items having the current basket number, the one in Session state.
    • If there are no such basket items, an error message is shown and most of the other controls on the form are made invisible, as it would now make no sense to see or to use them.
    • Otherwise, the data from the basket items retrieved is placed into the grid view. In addition, a For Each loop is used to add up the total cost of all the items in the basket as well as to add up the number of cars in the basket. The numbers to be added are obtained from basket item objects that the query produced.
    • These two computed numbers are displayed in labels on the form.
  • The Page_Load routine runs as soon as the Cart page is loaded. It starts by using the same code that the Results page used to be sure that the user has a shopper number. This is redundant and could probably be removed. Other than that, Page_Load displays an error message if there is no basket number in Session state. (As before, it checks to see if Session("BasketNum") Is Nothing, not if it equals the empty string.) If there is a BasketNum, this routine uses the UpdatePage routine to display the shopping basket data on the form.
  • The BuyButton_Click routine redirects to the Shipping.aspx form (which we will create in the next version of the car store). However, it only redirects to the new page if there are no errors showing on the current page.
  • The EmptyButton_Click routine is used when the user clicks the "Empty the Cart" button. It uses a LINQ query to find all of the basket items for the current basket number. A For Each loop is used to go through each of these basket item objects and mark them for removal. Then these changes are made permanent (which means that the data in the database is adjusted).
  • GridView1_PageIndexChanging is simple. It tells the grid view what new page index to use and then calls our UpdatePage routine to display the data correctly on the form.
  • Finally we reach the GridView1_RowDeleting routine. Its task is to delete the basket item for the row in which the user has clicked the Remove button. This is a bit tricky. Here are the steps:
    • e.RowIndex is used to tell us what row of the current page of the grid view is the row where the user clicked the Remove button.
    • By adding this to the grid view's grid size (maximum number of rows per page) times its page index (which page is currently displayed), we get the number within the overall grid view for the data in question. For example, if page index is 1 (really the second page of the grid view), page size is 3 (3 rows per page) and e.RowIndex is 2 (indicating the third item on the current page since numbering starts at zero), we then get 3 * 1 + 2 = 5 as the location, or index, for the desired row of data in our collection of objects. Since numbering starts at zero, 5 really means the 6th object in the collection.
    • A LINQ query is used to retrieve all of the basket items having the current basket number.
    • Then we use Skip(location), which would be Skip(5) in our example, to skip over the first 5 objects returned by the query. Then we apply Take(1) to obtain the first item from that result. This would be object 6, the one we want. Since we really obtain a collection that contains this one object, we use FirstOrDefault to get the actual object. From that object, we extract the ID number.
    • We then use that ID number to find the CarBasketItem object with that ID number among the CarBasketItem objects in our entities.
    • Finally, we mark it for removal and make that change permanent (that is, the change is made on the database end as well as here in our entity objects.


  • Now that most of the car store is working, it is time to start thinking of possible improvements to it. In CS 305, students are asked to make a few improvements that can be shown in the presentations to the class in finals week. These improvements do not have to be done now (in store 1, store 2, or even in the upcoming store3), though if you have time to do something now, that would make things easier later.
  • These improvements might include polishing the user interface. Perhaps better colors, font sizes, etc. could be used. Even nicer would be the use of CSS to specify items like this, or perhaps themes or skins. These are explained in our text and in many other ASP.NET books.
  • There are places where the code could be written simpler or better. Some suggestions for these are in the VB code as comments, and others may occur to you as you read through the code. Some clumsy coding was left in deliberately so that you would have something to work on!
  • If you can find any place where store 2 does not work, that itself is a plus. Better yet, if you can fix it, that is worth more.
  • If you can find a way to get the car store to work with the relationship in place between the CarBasketItem and CarBasket tables, that would be an improvement. Recall that I removed that relationship because it was interfering with the functioning of the code.
  • If you have the time to do so, you might consider adding the ability to customize the type of car that goes into a basket item. For example, you could allow the user to select the color from a list maintained in a table in your database. For some ideas on how to do this you can refer to the old VB 2005 directions for the second larger ecommerce app. Look for its use of product attributes, the tables it used, the coding, etc. Note, however, that this old app gave the user the ability to customize both the car color and the type of seat covering. It would be simpler to just have one item that can be customized. If you choose to do this, you should use LINQ instead of stored procedures, if possible.
  • Car store 3, the next iteration of this web application, may suggest further possible improvements.
  • Car store 4 will be your final version, with all of your improvements.
  • Students in CS 305 should email me a list of the improvements they made to the car store no later than our final exam period (which we use for the final project presentations). This is to make sure that I am aware of all of your improvements and thus give you credit for doing these.

Back to the main page for ASP.NET 4.5 Web Apps

Author: Br. David Carlson
Last updated: November 08, 2017