The Congo.com Application

The Congo.com Application

The application pictured in Figure 9-13 breathes life into many of the concepts presented in this chapter. Called Congo.com, it s a virtual storefront for a fictitious online bookseller. Congo.com s catalog consists of titles obtained from the SQL Server Pubs database. The main page, Congo.aspx, fetches the titles from the database and displays them in a DataGrid. Each row in the DataGrid contains an Add to Cart button that, when clicked, adds the corresponding book to a virtual shopping cart. Clicking the View Cart button at the top of the page shows the shopping cart s contents, again using a DataGrid. This DataGrid has Remove buttons that delete items from the shopping cart.

Figure 9-13

Congo.com.

Here s how to install Congo.com on your Web server:

  • Copy Web.config, Global.asax, Congo.aspx, and ViewCart.aspx to wwwroot or the virtual directory of your choice.

  • Compile Congo.cs and place the resulting DLL in the virtual directory s bin subdirectory. The following command performs the compilation:

    csc /t:library congo.cs

Once deployment is complete, call up Congo.aspx in your browser and click a few Add to Cart buttons. Then click View Cart to view your shopping cart s contents. Now do the same using a second instance of your browser. You ll find that the two browser instances track items added to the shopping cart independently. Why? Because each represents a separate session and is therefore assigned its own session (and own session state) on the server.

Inside Congo.com

Congo.com s source code, shown in Figure 9-14, is remarkably compact considering the amount of functionality it provides. If you don t believe me, try coding the application as an ISAPI DLL. You ll see what I mean.

The action begins in Global.asax. Each time a new user requests a page from the site, ASP.NET creates a session for that user and calls Global.asax s Session_Start handler. Session_Start creates a new ShoppingCart object to serve as a container for the user s selections and stores a reference to it in session state, keying it with the name MyShoppingCart :

Session["MyShoppingCart"] = new ShoppingCart ();

When the user calls up Congo.aspx and clicks an Add to Cart button, Congo.aspx s OnItemCommand method is called on the server. OnItemCommand retrieves the product ID, title, and price of the corresponding book from the DataGrid and encapsulates them in a BookOrder object:

BookOrder order = new BookOrder (e.Item.Cells[0].Text, e.Item.Cells[1].Text, Convert.ToDecimal (e.Item.Cells[2].Text.Substring (1)), 1);

OnItemCommand then retrieves the reference to the user s ShoppingCart from session state and adds the BookOrder to the ShoppingCart:

ShoppingCart cart = (ShoppingCart) Session["MyShoppingCart"]; if (cart != null) cart.AddOrder (order);

Congo.aspx s Page_Load handler populates the DataGrid by binding it to a DataSet holding the results of a database query.

When the user clicks the View Cart button at the top of the page, Congo.com redirects the user to ViewCart.aspx with Response.Redirect:

Response.Redirect ("ViewCart.aspx");

ViewCart.aspx declares a DataGrid that s similar to the one declared in Congo.aspx. But ViewCart.aspx s DataGrid control doesn t bind to a DataSet encapsulating the results of a database query; it binds to the ShoppingCart object in session state. Here s the code that does the binding:

ShoppingCart cart = (ShoppingCart) Session["MyShoppingCart"]; . . . MyDataGrid.DataSource = cart.Orders; MyDataGrid.DataBind ();

Clearly, ShoppingCart plays a huge role in Congo.com s operation. Not only does it keep a record of the items the user selected, but it implements an Orders property that supports data binding. Where does ShoppingCart come from, and what s the magic that enables it to work with data-binding controls?

ShoppingCart is a custom data type defined in Congo.cs. It s accompanied by BookOrder, which is also defined in Congo.cs. The ShoppingCart class is basically a wrapper around a Hashtable. It implements a private field named _Orders that holds a Hashtable reference, and public methods that enable BookOrder objects to be added to the Hashtable and removed. It also implements a public property named Orders that exposes the Hashtable s ICollection interface:

public ICollection Orders { get { return _Orders.Values; } }

That s why a DataGrid can bind to a ShoppingCart: because its Orders property exposes the underlying Hashtable s ICollection interface. The statement

MyDataGrid.DataSource = cart.Orders;

does nothing more than put the Hashtable s ICollection interface into the hands of the DataGrid.

Both ShoppingCart and BookOrder are tagged with Serializable attributes. That s so they can be stored in session state regardless of the session state process model selected. As I said earlier, it s wise to mark types that you intend to store in session state as serializable so that your source code doesn t have to change if the process model changes.

What role does Web.config play in Congo.com s operation? It stores the connection string that Congo.aspx uses to connect to the Pubs database. Storing the connection string in Web.config rather than hardcoding it into Congo.aspx enables it to be changed without modifying any C# code.

Web.config

<configuration> <appSettings> <add key="connectString" value="server=localhost;database=pubs;uid=sa;pwd=" /> </appSettings> </configuration>
Figure 9-14

Congo.com source code.

Global.asax

<script language="C#" runat="server"> void Session_Start () { Session["MyShoppingCart"] = new ShoppingCart (); } </script>

Congo.aspx

<%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <html> <body> <h1>Congo.com</h1> <form runat="server"> <table width="100%" bgcolor="teal"> <tr> <td> <asp:Button Text="View Cart" OnClick="OnViewCart" RunAt="server" /> </td> </tr> </table> <br> <center> <asp:DataGrid AutoGenerateColumns="false" CellPadding="2" BorderWidth="1" BorderColor="lightgray" Font-Name="Verdana" Font-Size="8pt" GridLines="vertical" Width="90%" OnItemCommand="OnItemCommand" RunAt="server"> <Columns> <asp:BoundColumn HeaderText="Item ID" DataField="title_id" /> <asp:BoundColumn HeaderText="Title" DataField="title" /> <asp:BoundColumn HeaderText="Price" DataField="price" DataFormatString="{0:c}" HeaderStyle-HorizontalAlign="center" ItemStyle-HorizontalAlign="right" /> <asp:ButtonColumn HeaderText="Action" Text="Add to Cart" HeaderStyle-HorizontalAlign="center" ItemStyle-HorizontalAlign="center" CommandName="AddToCart" /> </Columns> <HeaderStyle BackColor="teal" ForeColor="white" Font-Bold="true" /> <ItemStyle BackColor="white" ForeColor="darkblue" /> <AlternatingItemStyle BackColor="beige" ForeColor="darkblue" /> </asp:DataGrid> </center> </form> </body> </html> <script language="C#" runat="server"> void Page_Load (Object sender, EventArgs e) { if (!IsPostBack) { string ConnectString = ConfigurationSettings.AppSettings["connectString"]; SqlDataAdapter adapter = new SqlDataAdapter ("select * from titles where price != 0", ConnectString); DataSet ds = new DataSet (); adapter.Fill (ds); MyDataGrid.DataSource = ds; MyDataGrid.DataBind (); } } void OnItemCommand (Object sender, DataGridCommandEventArgs e) { if (e.CommandName == "AddToCart") { BookOrder order = new BookOrder (e.Item.Cells[0].Text, e.Item.Cells[1].Text, Convert.ToDecimal (e.Item.Cells[2].Text.Substring (1)), 1); ShoppingCart cart = (ShoppingCart) Session["MyShoppingCart"]; if (cart != null) cart.AddOrder (order); } } void OnViewCart (Object sender, EventArgs e) { Response.Redirect ("ViewCart.aspx"); } </script>

ViewCart.aspx

<html> <body> <h1>Shopping Cart</h1> <form runat="server"> <table width="100%" bgcolor="teal"> <tr> <td> <asp:Button Text="Return to Shopping" OnClick="OnShop" RunAt="server" /> </td> </tr> </table> <br> <center> <asp:DataGrid AutoGenerateColumns="false" CellPadding="2" BorderWidth="1" BorderColor="lightgray" Font-Name="Verdana" Font-Size="8pt" GridLines="vertical" Width="90%" OnItemCommand="OnItemCommand" RunAt="server"> <Columns> <asp:BoundColumn HeaderText="Item ID" DataField="ItemID" /> <asp:BoundColumn HeaderText="Title" DataField="Title" /> <asp:BoundColumn HeaderText="Price" DataField="Price" DataFormatString="{0:c}" HeaderStyle-HorizontalAlign="center" ItemStyle-HorizontalAlign="right" /> <asp:BoundColumn HeaderText="Quantity" DataField="Quantity"  HeaderStyle-HorizontalAlign="center" ItemStyle-HorizontalAlign="center" /> <asp:ButtonColumn HeaderText="Action" Text="Remove" HeaderStyle-HorizontalAlign="center" ItemStyle-HorizontalAlign="center" CommandName="RemoveFromCart" /> </Columns> <HeaderStyle BackColor="teal" ForeColor="white" Font-Bold="true" /> <ItemStyle BackColor="white" ForeColor="darkblue" /> <AlternatingItemStyle BackColor="beige" ForeColor="darkblue" /> </asp:DataGrid> </center> <h3><asp:Label ID= "Total" RunAt="server" /></h3> </form> </body> </html> <script language="C#" runat="server"> void Page_Load (Object sender, EventArgs e) { ShoppingCart cart = (ShoppingCart) Session["MyShoppingCart"]; if (cart != null) { MyDataGrid.DataSource = cart.Orders; MyDataGrid.DataBind (); Total.Text = String.Format ("Total Cost: {0:c}", cart.TotalCost); } } void OnItemCommand (Object sender, DataGridCommandEventArgs e) { if (e.CommandName == "RemoveFromCart") { ShoppingCart cart = (ShoppingCart) Session["MyShoppingCart"]; if (cart != null) { cart.RemoveOrder (e.Item.Cells[0].Text); MyDataGrid.DataBind (); Total.Text = String.Format ("Total Cost: {0:c}", cart.TotalCost); } } } public void OnShop (Object sender, EventArgs e) { Response.Redirect ("Congo.aspx"); } </script>

Congo.cs

using System; using System.Collections; [Serializable] public class BookOrder { string _ItemID; string _Title; decimal _Price; int _Quantity; public string ItemID { get { return _ItemID; } set { _ItemID = value; } } public string Title { get { return _Title; } set { _Title = value; } } public decimal Price { get { return _Price; } set { _Price = value; } } public int Quantity { get { return _Quantity; } set { _Quantity = value; } } public BookOrder (string ItemID, string Title, decimal Price, int Quantity) { _ItemID = ItemID; _Title = Title; _Price = Price; _Quantity = Quantity; } } [Serializable] public class ShoppingCart { Hashtable _Orders = new Hashtable (); public ICollection Orders { get { return _Orders.Values; } } public decimal TotalCost { get { decimal total = 0; foreach (DictionaryEntry entry in _Orders) { BookOrder order = (BookOrder) entry.Value; total += (order.Price * order.Quantity); } return total; } } public void AddOrder (BookOrder Order) { BookOrder order = (BookOrder) _Orders[Order.ItemID]; if (order != null) order.Quantity += Order.Quantity; else _Orders.Add (Order.ItemID, Order); } public void RemoveOrder (string ItemID) { if (_Orders[ItemID] != null) _Orders.Remove (ItemID); } }

On Your Own

Congo.com uses the default session time-out, which normally equals 20 minutes. To experience the impact of shortened time-out intervals firsthand, add the following statement to Web.config:

<sessionState timeout="1" />

Call up Congo.aspx, click a few Add to Cart buttons, verify that the items were added to the shopping cart, and return to Congo.aspx. Now wait a couple of minutes and check the shopping cart again. Because the session time-out is a mere 1 minute, the cart should be empty. Finish up by deleting the sessionState element from Web.config in order to reset the time-out interval to 20 minutes.

Because it lacks a Web.config file specifying otherwise, Congo.com settles for the default session state process model. To demonstrate the effect of moving session state out of Aspnet_wp.exe, try this simple experiment:

  1. Open Congo.aspx in your browser.

  2. Add a few items to the shopping cart.

  3. Open a command prompt window and restart IIS by typing iisreset.

  4. View the shopping cart. How many items does it contain?

The answer should be zero because restarting IIS restarts ASP.NET, and restarting ASP.NET shuts down Aspnet_wp.exe. Since that s where session is stored in the in-proc model, restarting IIS destroys all active session state, too. Now do the following:

  1. In a command prompt window, type

    net start aspnet_state

    to start the ASP.NET state server process running.

  2. Add the following statement to the system.web section of Web.config:

    <sessionState mode="StateServer" stateConnectionString="tcpip=localhost:42424" />

  3. Bring up Congo.aspx in your browser and add a few items to your shopping cart.

  4. Type iisreset again to restart IIS.

  5. Check your shopping cart.

This time, the shopping cart s contents should still be there because session state is no longer stored in Aspnet_wp.exe. It s in Aspnet_state.exe, which isn t restarted when ASP.NET is restarted. If you go the extra mile and move the state server process to another machine (or use a SQL Server database on another machine to store session state), you can reboot your entire server without losing session state.

As a final learning exercise, try modifying Congo.aspx to store the DataSet that it binds to the DataGrid in the application cache. As it stands now, a physical database access is performed every time Congo.aspx is requested. Assuming the contents of the database don t change very often, it would be far more efficient to query the database periodically, store the results in the application cache, and populate the DataGrid from the cache. Here s a blueprint for making the change:

  1. Add an Application_Start method to Global.asax.

  2. In Application_Start, populate a DataSet with a database query and add the DataSet to the application cache. Specify that the DataSet expires 5 minutes after it s added to the cache, and provide a reference to a callback method that s called when the DataSet expires.

  3. Code the callback method to reexecute the database query and place a new DataSet in the application cache.

  4. Modify Congo.aspx s Page_Load handler to bind to the DataSet stored in the application cache rather than populate a new DataSet with the results of a database query.

Once these changes are made, a physical database access will occur only every 5 minutes, no matter how often the page is requested. The performance difference will be negligible if you have only a few users, but as the load on the server increases, the improvement will be more and more noticeable. Caching frequently used data in memory is a tried-and-true means of increasing performance, and ASP.NET s application cache is the perfect tool for the job.



Programming Microsoft  .NET
Applied MicrosoftNET Framework Programming in Microsoft Visual BasicNET
ISBN: B000MUD834
EAN: N/A
Year: 2002
Pages: 101

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net