Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Generic Shopping Basket

0.00/5 (No votes)
28 Dec 2003 1  
A generic shopping basket web control that should serve as a good starting point to roll your own, more advanced version.

Introduction

The web seems to be teeming with different shopping baskets and different ways (PHP, ASP, JScript..) to solve the problem. I thought it would be fun to write a generic shopping basket as a nice, standalone web control. To whole idea was to make a web control that includes as much "shopping basket logic" as possible so that it needed minimal coding to include in an existing or new ASP.NET project. One of my aims was also to keep the code relatively light so that the control could be used as a kind of introduction to developing controls in C# and maybe even as a tutorial on some very common .NET and C# techniques. The result turned out pretty good and I am certainly convinced that even beginning .NET programmers will be able to understand all of the code. Of course, there are many ways to change and improve some of the concepts in the web control. I will mention possible improvements in the relevant sections so you can use this control as a template to add new functionality that better suits your needs. There really is a whole lot of extra you can do with web controls but some of these are beyond the scope of the article. So let's start shopping...

Background

When I designed the control, I had to make certain decisions since "generic" certainly implies some kind of open design while at the same time introducing some kind of limited functionality. First of all, I did not want my control to use Viewstate to pass settings from page to page because I wanted a design that enabled a web designer to drop the basket on any webpage and have it display its content correctly. Now there are a couple of ways to do this but saving the basket in a session state looked like the best generic solution. Note that session state in .NET is not so evil anymore as it was in normal ASP. Session state can now easily be used in clustered web environments.

.NET gives me the option to serialize classes in a binary format or in an XML format. I decided to go the XML way because it would enable developers to read the basket content from session state (of course, you can also use the control to do that when you have it available on your ASP.NET page). I did not use built-in serialization because it has trouble handling private members and I prefer to roll my own serialization from time to time. Note that you can implement binary serialization if you want to make the basket session state smaller.

The second big decision was to use a composite control (a control that contains a host of other child .NET controls) but still handle a big part of the rendering myself. This results (according to MS) in a faster control but I really did it because I could see how the actual HTML was being spit out by the control. Of course, this also makes the look and feel of the control a bit easier to adapt. If you don't like the way it looks, just change the Render code. The host control logic made it a bit easier to catch events (with OnBubbleEvent), and the INamingContainer interface makes sure that children have unique IDs.

Using the code

Using this control should be pretty straightforward. Just add it as a web control to the VS2003 toolbar and drop it on an ASP.NET webpage. You should provide logic on your webpage to add products because there is no way the web control can know where these products come from. The main things you need to remember is that the control itself handles modifying quantities, removing products and emptying the basket. Also, the control provides a handler that you can use when the user pushes the Checkout button of the basket. The basket does not display any button when it is set to "disabled", so this state can be used to just display the content (on a checkout page, for example) of the basket. The basket saves all its information in session state, so you can use the basket on any page in your website and it will be able to read the content of the shopping basket of the logged on user. Here is some sample code that illustrates how you could use the control on an aspx.cs page.

protected GenericBasket.Basket Basket1;

// Events will be added in design mode or you can do it manually

// Example : this.Basket1.Checkout += new System.EventHandler(

// this.Page_Checkout);


private void Page_Checkout(object sender, System.EventArgs e)
{
  // disable the basket so that no buttons can be pressed

  Basket1.Enabled = false;
  // do some other stuff to checkout

  // add an item that includes the Shipping & Handling fee

  // I just add 10% of the total cost for demo purposes

  Basket1.Remove("SHIPPING");
  Basket1.Add("SHIPPING","+ Shipping & Handling",1,Basket1.TotalPrice/10);
}

private void Page_Load(object sender, System.EventArgs e)
{
  if (!IsPostBack)
  {
    // Add some products

    Basket1.Add("1","XBox",2,199);
    Basket1.Add("2","GameCube",1,99);
    Basket1.Add("3","PS2",1,199);
    Basket1.Add("4","Remove me",1,0);
    // and remove a product again

    Basket1.Remove("4");
    // of course you can also use the product class

    GenericBasket.Product newProduct = new GenericBasket.Product(
        "4","GameBoy Advance",1,140);
    Basket1.Add(newProduct );
    }
  }
}

The GenericBasket.Product class

The methods and properties that are public (i.e. that you can use on ASP.NET pages) have been kept to a minimum. The Product class, however, is made public so you can use it as a wrapper for most of your product manipulations. I have kept the Product class very basic because there are so many different properties that you could attach to a product and most of them are application specific. The design of the control assumes that you will get the product specific information (tax, location, stock....) yourself and do something with that on the checkout cycle.

Of course, you can easily add some more specific stuff to the Product class if you want the shopping basket to contain more business logic. The Product class implements the ICompareable interface. This interface provides sorting so we can easily sort an array of Product elements.

Getting information out of the basket

The basket supports (read-only) indexers and enumerators so it behaves very much like a normal array and/or collection. The following code is quite valid:

  Response.Write(Basket1.Count.ToString());
  Response.Write(" Items in Basket");
  foreach (GenericBasket.Product itemProduct in Basket1)
  {
    Reponse.Write("*");
    Response.Write(itemProduct.sName);
    Response.Write(" ");
  }
  // Get the product with ID "9"

  if (Basket1["9"] != null)
    Response.Write("Product ID 9 found");
  else
    Response.Write("Product ID 9 not found");

Implementing these was quite straightforward. The enumerator does not need a lot of code, basically you can just return the existing enumerator from the ArrayList member. Don't forget to add the IEnumerable interface to support enumerators. Likewise, supporting indexers was a breeze as well, with just some simple code to support an integer index and a string index like most .NET classes. Here is the code of one indexer, the string version is very similar:

  public Product this [int index]   // Indexer declaration

  {
    get 
    {
      if (index >= 0 && index < m_aProducts.Count)
        return(m_aProducts[index] as Product);
      else
    return(null);
    }
  }

Events

With event bubbling, the control can make sure some events "bubble" up to the parent control. In the case of our generic basket, I decided to bubble only two events to the top. To support event bubbling, you need to implement the INamingContainer interface.

  public event EventHandler Checkout; // Event that is triggered 

    // when checkout button is pressed

  public event EventHandler Changed;  // Event that happens when 

    // control changes *itself*

To correctly process all the commands that are fired by the control, you need to specify the CommandArgument attribute to all child controls that you create. For example, on a Button:

  btnGeneric = new Button();
  btnGeneric.Text = "Delete";
  btnGeneric.CommandName = "Delete";
  btnGeneric.CommandArgument = outputProduct.sID;
  btnGeneric.CssClass = CssClass;
  Controls.Add(btnGeneric);

And this can then result in a bubbled event processing method like this:

protected override bool OnBubbleEvent(object source, EventArgs e) 
{   
  bool handled = false;
  if (e is CommandEventArgs)
  {
    CommandEventArgs ce = (CommandEventArgs)e;
    if (ce.CommandName == "Delete")
    {
      Remove((string)ce.CommandArgument);                
      OnChanged(ce);
      handled = true;   
    }
  }
  return(handled);    
}

protected virtual void OnChanged (EventArgs e)
{
  if (Changed != null)
  {
    Changed(this,e);
  }
}

Composite control?

I chose to go the way of the composite control (as opposed to a control where I render all HTML elements myself and handle the postback results) because I think that .NET handles events fired from children fairly well and this results in easier code. Of course, a composite control is a bit slower and has some small pitfalls. If you really experience performance issues, you might have to completely redesign the control so that it takes care of its own rendering and support postback data processing (implement IPostBackDataHandler).

I did run into one serious problem. Apparently the .NET framework calls CreateChildControls before it processes the events fired by the child controls. This resulted in very weird behavior and wrong controls containing wrong information. The solution was quite simple (but not really satisfactory). I just added the CreateChildControls call in the PreRender event. Why not satisfactory? Well, if you step through the code with the debugger, you will see that CreateChildControls is now called twice. Not very sensitive performance-wise, is it? I have left this for now because I suspect there is a more elegant way of solving this problem.

Public methods

Here is a list of the methods you can use on the basket and a short description:

public System.Int32 Add ( GenericBasket.Product newProduct ); 
  // Add a new Product to the basket. Returns index at which 

  // the value has been added.

public System.Int32 Add ( System.String sProductID , 
    System.String sName , System.Int32 iQuantity, System.Single fPrice); 
     // Add a new product to the basket alternative version. 

     // Returns index at which the value has been added.

public Basket ( ); // Constructor, nothing interesting to 

    //say about this one ;)

public void Clear ( ); // Clears the content of the basket

public int Count [  get]; // The number of items in the basket

public string EmptyTitle [  get,  set ]; // The title of control 

   // when it is empty

public System.Collections.IEnumerator GetEnumerator ( ); 
    // returns an enumerator, used for implementing enumeration

public void Remove ( System.String sProductID ); // Removes a product 

   // from the basket based on product ID

public string SessionKey [  get]; // The "session key" that you 

  // can use to read the basket session information XML

public const GenericBasket.Product this [  get]; // Indexers, 

  // one takes int index and the other one takes a product ID string

public string Title [  get,  set ]; // The title of your basket 

   // (when there are items inside)

// New in version 1.1

public float TotalPrice [  get]; // Gets the total price of all items in basket

public System.Globalization.CultureInfo Culture [  get,  set ]; 
  // the Culture of the basket, this makes sure the 

  // basket can display price in correct format

Points of Interest

I must add that this is my first .NET contribution to CodeProject. I made the switch to .NET from C++ about a year ago (of course, I still use C++ a lot but mainly at home) and discovered quickly that the .NET framework is a vast new maze with different paths to solve different problems. During the writing of this control, I constantly found different approaches and before you start modifying this control, you should think about some new and better ways to improve on the design (it is a generic control after all).

  • You can use more advanced child controls (DataGrid, for example) to display the contents of the basket.
  • You could even bind the XML session data to the DataGrid. This would give you a very good looking basket with some nice DataGrid functionality.
  • Adding database saving/loading would also be good since visitors of your website might like it if their basket stays filled between visits. This can be implemented in the control or it can be exposed as an event to the parent (just like checkout).
  • The Product class is pretty basic and could be extended. What about an URL to the product page or maybe a link to a picture?
  • Some business logic could be incorporated. For example, automatically adding tax and/or shipping costs.
  • There is a lot that can be done with the layout that is pretty basic right now. Modifying styles with a CSS stylesheet is of course very doable, maybe you could add extra HTML class tags to some of the HTML elements in the Render method, or maybe you can give the control templated properties which allow a page developer to customize the UI for the control. Remember that the control, at the moment, propagates the CssClass attribute to all of the HTML elements. This gives you some layout control, but more might be needed.

If you decide to use a shopping basket on your website, I hope this article gave you some ideas and useful code. If anybody decides to improve the design, let me know so I can incorporate them in newer versions of the article. Have fun shopping!

History

  • Version 1.1:
    • Fixed a minor bug in the indexer.
    • Added price information.
    • Added culture information, so that the basket will know how to display price information.
    • Changed some variable names to make them more ".NET compliant".
  • Version 1.2
    • Fixed a small bug in the HTML output when basket was empty (the last TD tag was not closed).
    • Added some "&nbsp;" between table elements to make table look consistent.
    • Added false)> to some properties that shouldn't really appear in the Properties window.
    • Session key getter is now static so you can query basket session key info without creating basket instance.
    • Changed some TD tags into TH tags to allow better stylesheet layout control of basket.
    • Changed the basket table color in HTML output if no color was defined, to white/black.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here