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...
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;
private void Page_Checkout(object sender, System.EventArgs e)
Basket1.Enabled = false;
Basket1.Add("SHIPPING","+ Shipping & Handling",1,Basket1.TotalPrice/10);
private void Page_Load(object sender, System.EventArgs e)
GenericBasket.Product newProduct = new GenericBasket.Product(
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
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(" Items in Basket");
foreach (GenericBasket.Product itemProduct in Basket1)
if (Basket1["9"] != null)
Response.Write("Product ID 9 found");
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]
if (index >= 0 && index < m_aProducts.Count)
return(m_aProducts[index] as Product);
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
public event EventHandler Checkout;
public event EventHandler Changed;
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
btnGeneric = new Button();
btnGeneric.Text = "Delete";
btnGeneric.CommandName = "Delete";
btnGeneric.CommandArgument = outputProduct.sID;
btnGeneric.CssClass = CssClass;
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")
handled = true;
protected virtual void OnChanged (EventArgs e)
if (Changed != null)
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
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.
Here is a list of the methods you can use on the basket and a short description:
public System.Int32 Add ( GenericBasket.Product newProduct );
public System.Int32 Add ( System.String sProductID ,
System.String sName , System.Int32 iQuantity, System.Single fPrice);
public Basket ( );
public void Clear ( );
public int Count [ get];
public string EmptyTitle [ get, set ];
public System.Collections.IEnumerator GetEnumerator ( );
public void Remove ( System.String sProductID );
public string SessionKey [ get];
public const GenericBasket.Product this [ get];
public string Title [ get, set ];
public float TotalPrice [ get];
public System.Globalization.CultureInfo Culture [ get, set ];
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
- 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).
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!
- 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 " " between table elements to make table look consistent.
[Browsable(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.