Beginning ASP.NET 1.1 with Visual C# .NET 1002003 [Electronic resources]

Chris Ullman

نسخه متنی -صفحه : 220/ 99
نمايش فراداده

Remembering Information in a Web Application

Whenever you browse to a Web page, you establish a connection with the Web server for a brief moment while the page is sent to your browser. After this, the Web server forgets all about you. This is because HTTP, the protocol used for surfing the Web, is a stateless protocol. A stateless protocol is used where it is impractical to maintain a constant connection between the server and the client. Messages are sent back and forth using a request-response mechanism. The client makes requests and the server sends responses. After each response is sent by the server, the connection is dropped.

Unless some external mechanism is in place to associate a client with a known list of clients stored on the server, proactive client-specific communication from the server is not possible. One of the reasons for which HTTP is stateless is that it would be impossible for a Web server to remember everything about every single visitor to the site – imagine how the BBC news Web site would cope with remembering the details of every one of the millions of readers that visit to catch up with the headlines! Statelessness essentially means that you cannot log in to a site using only HTTP. Thankfully, there are tools available that make it possible for the Web server to remember information.

When you browse any Web site, you leave a trail of information with the Web server about the pages you are viewing and where you are connecting from. This information, combined with some code on the server (and some help from the client), enables Web servers to remember visitor information. What Web servers do with that data depends on how you configure them, but as a user, you are most likely to see the result of this process in the form of a personalized browsing experience. For example, you can buy stuff from online stores or log in to community Web sites and participate in online forums. Personalization comes as a result of being able to remember user information and making it useful.

It's not just information about users that needs to be stored – often you might need to access common code that uses data stored in memory from every page on the site. This data could be any kind of object, from a simple string variable to a

DataSet . Because of the stateless mechanism used to serve pages to browsers (the HTTP protocol), pages have a limited lifespan. Thus, persisting information is a problem you are likely to encounter as you develop ASP.NET sites. Learning to overcome the stateless nature of HTTP is important, and not very difficult, as you'll see when you work through examples in this chapter.

There are four different mechanisms for remembering information:

Cookies: Identifying previous visitors to a site by storing data on the client machine

Sessions: Remembering information for the duration that a user browses a site

Applications: Remembering information that exists for as long as the application runs

Caching: Storing data for as long as is necessary to improve performance

Each of these mechanisms fulfills a different purpose, so it is important to understand which mechanism should be used to remember information and not just how each mechanism works. Let's start the discussion by looking at how cookies work.

Cookies

Cookies are used throughout the Web to store small pieces of information on the client machine. They are small text files that usually store persistent data, which is useful whenever you revisit a site. This can be data such as user preferences and login tokens, whether a user has voted in an online poll, details of the last time you browsed a site, and so on. In short, cookies contain dat that allows a Web server to identify users based on their visiting history. Cookies are designed so that only the site that created them can read them. If you look at the

<drive>\Documents and Settings\<UserName>\Cookies folder on your hard drive, you'll notice that many cookies reside on your system. Each of them has some kind of identifier in its name that indicates the site that created them.Figure 11-1 shows that I've visited the BBC, Computer Manuals, and Firebox.com sites. You can open a cookie using MS Notepad. The information contained in any of these cookies, usually some text, will give you clues about the site that created it.

Figure 11-1

Taking the BBC news site as an example, I've in the past checked a button to say that I'm in the UK (so I get the UK front page by default whenI visit the site). I've also specified that when I view the http://www.bbc.co.uk/weather site, my hometown be set to Birmingham, UK. If I look inside the BBC cookie, I can see bits and pieces of text (among the other text) that look as if they correspond to those choices:

... BBCNewsAudienceDomestic ... BBCWEACITYuk1045 ...

If I change the default page preference to BBC News Audience International and the hometown to Nottingham, the relevant information in the cookie changes as follows:

... BBCNewsAudienceInternational ... BBCWEACITYuk2803 ...

By modifying preferences on the Web site, a file on my hard drive has been updated. This means that when I next visit the site, it will remember my preferences. Let's look at how this happens.

How Do Cookies Work?

Cookies are linked to the request-response mechanism of HTTP and are passed back and forth along with other data between the client and the server. Let's look at what happens where a site uses cookies to remember whether a user wants a certain popup when they visit a site.In Figure 11-2, Stage 1 is about a user visiting a page on the site. Stage 2 is when the contents of that site are sent to the browser. These contents happen to include a popup. At Stage 3, anyone browsing the site could check a box on a form that states, "Do not show the advert popup again." When they click a button to submit their request, they send data back to the server. Finally, the server sends a small cookie to the client machine. Figure 11-3 represents what happens when the user requests the page again.:

Figure 11-2

Figure 11-3

This time round, the user won't see the popup ad when browsing the site. This is because the server will have read the cookie that the client sends across while requesting the page and know that the user expressed a preference not to see the popup. The client sends this cookie each time the user visits the site.

At this point, you need to understand that the client cannot be trusted. The client may not send the cookie, send a dodgy cookie, or a cookie that has prematurely expired. Different Internet settings and browsers mean that you can't always rely on cookies. If they were reliable, you could store all kinds of login data in a cookie. However, in practice, you should treat the information contained within them as 'it would be nice if you could remember this.' For secure and reliable storage of information, use cookies in combination with features such as sessions and data stored in a database.

When you save a cookie onto the client machine, you can specify how long it should exist. Cookies can persist indefinitely, or they can be set to expire after a certain time. In the popup example, you could specify that the cookie will expire after a month, by which time a new advert will have appeared. The user will have to recreate the cookie if they wanted to block the popup.

Note that users also can manually delete cookies from their system. If you have Internet Explorer, you can delete all the cookies on your machine by selecting Tools | Internet Option from the main IE window, and then clicking Delete Cookies, as shown in Figure 11-4:

Figure 11-4

Of course, if I do this, I would need to tell the BBC site again that I wanted the UK version of the site and weather details for Birmingham. Moreover, you will find you have to log on to all the sites where you have selected the Remember my details option.

As the earlier lifecycle diagrams hinted, you can set the cookie state using the

Response object and read the existing cookie state using the

Request object. It's quite simple to do this, so let's look at an example and see these objects working their magic!

Try It Out - Using Cookies

In this example, we're going to add to the Wrox United application once again. In the previous chapter, we added a box that enabled the user to register for email updates about changes made to the

Default.aspx page. However, this page didn't do anything with this information other than validate it. Let's get this working now.

Open up

Default.aspx in Web Matrix and you will see the page in its current state, as shown in Figure 11-5:

Figure 11-5

In the

WroxUnited database there is a simple table called

Fans that will contain email addresses of fans of the team. When the Register button is clicked, we need to do three things:

Check that the address hasn't been added to the database. If it has, create a cookie and update the display to indicate that the user has registered for updates.

If the address hasn't already been added, add it to the database, create a cookie, and indicate that the user has been registered for updates.

When the page is loaded, if the client has a cookie indicating that they have already registered for updates, display a message confirming this.

Switch to Code view – it's time to add some database code to check whether a user has registered already or not. Drag a SELECT Data Method wizard onto the page and select the

FanEmail field from the

Fans table where the

FanEmail field matches the

@FanEmail

parameter. Call this method

CheckFanEmailAddresses and save it as a

DataReader . Pass this into the function:

System.Data.IDataReader CheckFanEmailAddresses(string fanEmail)

{

string connectionString =

"Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; " +

"Data Source=C:\\BegASPNET11\\WroxUnited\\Database\\WroxUnited.mdb";

System.Data.IDbConnection dbConnection =

new System.Data.OleDb.OleDbConnection(connectionString);

string queryString = "SELECT [Fans].[FanEmail] FROM [Fans]" +

" WHERE ([Fans].[FanEmail] = @FanEmail)";

System.Data.IDbCommand dbCommand = new System.Data.OleDb.OleDbCommand();

dbCommand.CommandText = queryString;

dbCommand.Connection = dbConnection;

System.Data.IDataParameter dbParam_fanEmail =

new System.Data.OleDb.OleDbParameter();

dbParam_fanEmail.ParameterName = "@FanEmail";

dbParam_fanEmail.Value = fanEmail;

dbParam_fanEmail.DbType = System.Data.DbType.String;

dbCommand.Parameters.Add(dbParam_fanEmail);

dbConnection.Open();

System.Data.IDataReader dataReader =

dbCommand.ExecuteReader(System.Data.CommandBehavior.CloseConnection);

return dataReader;

}

This code needs to be altered slightly to fit our needs. Firstly, we need to change the connection string to use the database connection details stored in the

web.config file. Then we need to find out if a user has already registered, so let's modify this code to retrieve a count of the number of times that a particular email address appears in the database. If all goes well, this should never exceed

1 . We can then use this value to return a Boolean true (if the user already exists) or false.

To alter this code, change the following highlighted lines:

bool CheckFanEmailAddresses(string fanEmail)
{
string connectionString = 
ConfigurationSettings.AppSettings["ConnectionString"];
System.Data.IDbConnection dbConnection =
new System.Data.OleDb.OleDbConnection(connectionString);

string queryString = "SELECT COUNT([Fans].[FanEmail]) FROM [Fans]" +

"WHERE ([Fans].[FanEmail] = @FanEmail)"; System.Data.IDbCommand dbCommand = new System.Data.OleDb.OleDbCommand(); dbCommand.CommandText = queryString; dbCommand.Connection = dbConnection; System.Data.IDataParameter dbParam_fanEmail = new System.Data.OleDb.OleDbParameter(); dbParam_fanEmail.ParameterName = "@FanEmail"; dbParam_fanEmail.Value = fanEmail; dbParam_fanEmail.DbType = System.Data.DbType.String; dbCommand.Parameters.Add(dbParam_fanEmail);

int result = 0;

dbConnection.Open();

try

{

result = (int)dbCommand.ExecuteScalar();

}

finally

{

dbConnection.Close();

}

if (result > 0)

{

return true;

}

else

{

return false;

} }

Notice the changes made to the return type of the function, the SQL, and the method used to query the data source. This will be discussed in a moment.

Switch back to the Design view and double-click the Register button at the top left to create an event handler for the

Click() event of the button. Add the following code:

void btnRegister_Click(object sender, EventArgs e)
{

string FanEmail = txtEmailAddress.Text;

//Check whether the email address is already registered

//If not, we need to register it by calling the AddNewFanEmail() method

if (!CheckFanEmailAddresses(FanEmail))

{

AddNewFanEmail(FanEmail);

}

// Email has been registered, so update display and attempt write to cookie

txtEmailAddress.Visible = false;

lblRegister.Text = "You have successfully registered for email updates";

btnRegister.Visible = false;

HttpCookie EmailRegisterCookie = new HttpCookie("EmailRegister");

EmailRegisterCookie.Value = FanEmail;

EmailRegisterCookie.Expires = DateTime.Now.AddSeconds(20);

Response.Cookies.Add(EmailRegisterCookie); }

Here, make note of the call to a function called

AddNewFanEmail() – let's create this now.

Drag an Insert Data Method code wizard onto the page, select the Fans table, click Next, and save the method as

AddNewFanEmail() .

Important

Don’t check the box next to the

FanEmail field when you are building this method. The correct parameter information will be created automatically if you totally ignore the field. All you need to do is select the

Fans table, click Next, and save the method.

Let's take a look at the code:

int AddNewFanEmail (string fanEmail)

{

string connectionString =

"Provider=Microsoft.Jet.OLEDB.4.0;Ole DB Services=-4;" +

"Data Source=C:\\BegASPNET11\\WroxUnited\\Database\\WroxUnited.mdb";

System.Data.IDbConnection dbConnection =

new System.Data.OleDb.OleDbConnection(connectionString);

string queryString = "INSERT INTO [Fans] ([FanEmail]) VALUES (@FanEmail)";

System.Data.IDbCommand dbCommand = new System.Data.OleDb.OleDbCommand();

dbCommand.CommandText = queryString;

dbCommand.Connection = dbConnection;

System.Data.IDataParameter dbParam_fanEmail =

new System.Data.OleDb.OleDbParameter();

dbParam_fanEmail.ParameterName = "@FanEmail";

dbParam_fanEmail.Value = fanEmail;

dbParam_fanEmail.DbType = System.Data.DbType.String;

dbCommand.Parameters.Add(dbParam_fanEmail);

int rowsAffected = 0;

dbConnection.Open();

try

{

rowsAffected = dbCommand.ExecuteNonQuery();

}

finally

{

dbConnection.Close();

}

return rowsAffected;

}

Change the connection string to use the central connection string:

int AddNewFanEmail (string fanEmail)
{

string connectionString =

ConfigurationSettings.AppSettings["ConnectionString"]; System.Data.IDbConnection dbConnection = new System.Data.OleDb.OleDbConnection(connectionString);

Finally, check whether a cookie is present when the page is loaded, so that you can change the display if the user has already registered. Add the following to the

Page_Load() method:

if (Request.Cookies["EmailRegister"] == null)

{

txtEmailAddress.Visible = false;

lblRegister.Text = "You have registered for email updates";

btnRegister.Visible = false;

}

Run the page now, enter your email ID, and click Register. The message will change (as long as you enter a valid email address) to indicate that you've registered, as shown in Figure 11-6.

Figure 11-6

Note that the expiration time on the cookie was set to 20 seconds, which resulted in your email address not being remembered for long. Quickly close your browser and reopen the page. You should see the message shown in Figure 11-7:

Figure 11-7

If you keep clicking Refresh after every 20 seconds, the textbox and button will reappear along with a message asking you to register.

How It Works

Let's start the discussion by looking directly at what happens when a user clicks the Register button. This is when the cookie is created on the client:

void btnRegister_Click(object sender, EventArgs e)
{
string FanEmail = txtEmailAddress.Text;
//Check whether the email address is already registered
//If not, we need to register it by calling the AddNewFanEmail() method
if (!CheckFanEmailAddresses(FanEmail))
{
AddNewFanEmail(FanEmail);
}

If the email address is already stored in the database, the

CheckFanEmailAddresses() method will return

true . However, if it returns

false , the email address is new and needs to be added to the database.

Once the email address has been stored in the database, we can go ahead and change the displayed text on the screen:

// Email has been registered, so update the display and attempt write to a 
cookie
txtEmailAddress.Visible = false;
lblRegister.Text = "You have successfully registered for email updates";
btnRegister.Visible = false;

Next we attempt to add a cookie at the client end:

HttpCookie EmailRegisterCookie = new HttpCookie("EmailRegister");
EmailRegisterCookie.Value = FanEmail;
EmailRegisterCookie.Expires = DateTime.Now.AddSeconds(20);
Response.Cookies.Add(EmailRegisterCookie);

The first line of code creates a new instance of the

HttpCookie class and names it

EmailRegister . Once the cookie has been successfully created on the client, you can refer to the cookie by name later on in the code. The value of the cookie was set to be the email address entered into the textbox, and the

Expires property also was set. In this example, the cookie was set to expire 20 seconds from the time at which it was created, which isn't practical in a real world application! The

Expires property can be any

DateTime value, so you could, for example, change this to

AddMonths(6) so that the cookie persists for 6 months.

The

DateTime.Now property is the easiest way to get the current date and time on the server, so by calling the

AddSeconds() method, we can easily specify that a cookie will expire after a set amount of time from when it was created. For example, you may want to display this box every 6 months to fans in case their email address has changed, to ensure that they are registered correctly. Alternatively, you can hard-code a date (for example, if you wanted to create a monthly special).

We also added two data methods to check the status of the cookie. The first method was used to add a new email address to the database:

string queryString = "INSERT INTO [Fans] ([FanEmail]) VALUES (@FanEmail)";

The second data method queried the database to check whether the email address exists in the database. Let's look at the code:

bool CheckFanEmailAddresses(string fanEmail)
{

The first thing altered was the function return type. After all, we don't want a

DataReader in this case – we want a simple yes or no answer to whether the email address exists already, so the return type is set to return a Boolean

true or

false .

The next change is to the query used:

string queryString = "SELECT COUNT([Fans].[FanEmail]) FROM [Fans]" +
" WHERE ([Fans].[FanEmail] = @FanEmail)";

The

COUNT statement literally counts the number of results matched by the

SELECT statement. You should never end up with the result of a count being more than one because this check is performed every time we attempt to add a new email address. To be on the safe side, return

true to the calling code if you retrieve any rows at all:

int result = 0;
dbConnection.Open();
try
{
result = (int)dbCommand.ExecuteScalar();
}
finally
{
dbConnection.Close();
}
if (result > 0)
{
return true;
}
else
{
return false;
}

Hee, we added a quick test to the

Page_Load() event handler to see if a cookie exists on the client:

if (Request.Cookies["EmailRegister"] == null)
{

The

Request object is used to read the cookie; you can refer to a cookie by the name given to it earlier. If the cookie exists, this will return

true and you can hide the unnecessary registration functionality:

   txtEmailAddress.Visible = false;
lblRegister.Text = "You have registered for email updates";
btnRegister.Visible = false;
}

OK, so we've learned a lot about cookies and seen how to use cookies in a site. To recap, the general rules of thumb for using cookies are:

Use them to store small pieces of data that aren't crucial to your application.

Use them wisely – don't be tempted to store large objects in a cookie, because every request a client makes to your site will be accompanied by the cookie data.

Don't rely on them for storing secure user details; instead, keep them simple to help with preliminary identification. The Amazon Web site is a good example of how to use cookies. It uses cookies to remember information about you when you visit the site, but actually to buy stuff, you need to retype your password details. These are passed to secure servers that authenticate you.

Sessions

A session can be thought of as the total amount of time you spend browsing a site. For example, in an online store, you first visit the site, log on, buy some stuff, and then leave. A user session pertains to the interactions that occur when a single user browses a site. Information in a session is accessible only for as long as the session is active. You could, for example, store the name of the currently logged-in user in the

Session object specific to that user, and any of the pages in the site could then take this value and display it. Sessions are useful for features such as shopping baskets or any application that stores information on whether or not a user is logged in. They are tied in to a specific instance of a browser, so another instance of the browser on the same machine would not be able to access the same data.

It's a bit tricky to evaluate when a session ends, since when a browser closes, this information is not usually sent to the server (imagine having to wait for a 'close' signal to be sent whenever you closed a Web page!) To solve this problem, you can specify a timeout value for sessions. The default value is usually 20 minutes, which means that the session ends after 20 minutes of inactivity. This value can be changed; for example, if you created a financial Web site, you may want sessions to end after five minutes of inactivity. This minimizes the chances of sensitive information leaking even if a user forgets to log out or leaves the browser window open after a banking transaction.

How Do Sessions Work?

When a session starts, you can store data that will exist during that session. This could be simple text information about a user or an object such as an XML file. A 120-bit session identifier identifies each session. This session identifier is passed between ASP.NET and the client either by using cookies or by placing the identifier in the URL of the page (a technique that can be used for clients that have cookies turned off). Let's look at a session identifier – the following is an example of embedding a session identifier in the URL of a page:

http://www.mysite.com/(vgjgiz45ib0kqe554jvqxw2b)/Default.aspx

The extra data in the URL is only the session identifier – the actual data stored in the session is stored on the server. As a session can hold a variety of objects ( a string, an

ArrayList , even a

DataSet object), only the identifier is passed between the client and the server. Figure 11-8 shows how sessions work:

Figure 11-8

After the user logs on to an online store, the server knows who the user is and can relate each request from that user to the details specific to the user that are stored in the session. Therefore, the server can maintain a shopping basket and checkout functionality without asking the user to log back in with every request. Once the user has finished browsing the site, the server will eventually destroy the information stored in session memory and free up resources that it might need for other clients.

ASP.NET, by default, stores session information in the memory and in the same process as ASP.NET. This means that if the application crashes, session data will be lost. However, you do have the option to store session state information in a different process, on a different machine, or even in a database. This flexibility allows you to make your applications more robust in large-scale deployment scenarios.

The

Session object has quite a few methods that you can use:

Session.Add : Adds a new item to the

Session object

Session.Remove : Removes a named item from the session

Session.Clear : Clears all values from the session but leaves the session active

Session.Abandon : Ends the current session

Perhaps the simplest way to add data to a session is to use the following syntax:

Session["ItemName"] = Contents

After creating a new item in the session, you can use

ItemName to refer to the contents of its corresponding

Session object. The item is an identifying feature of the contents of the session, so you could store all the following in the session, where the first item is a simple string, the second is the string entered into a textbox, and the third is a

Hashtable object:

Session["Name"] = "Chris"

Session["Email"] = txtEmailAddress.Text

Session["ShoppingBasket"] = HashtableOfBasketItems

Let's look at sessions in action by using an example.Before you run this example, you will need three images –

shirt.gif ,

hat.jpg , and

mascot.jpg . These three files are available along with the rest of the source code for this book from the Wrox Web site.

Try It Out - Using Session State

This example will add another new page called

Merchandise.aspx to the site. Add this new blank ASP.NET page to the Wrox United application folder.

Add a heading 1 with the text Wrox United at the top of the page, and directly underneath that, add a heading 2 with the text Official Merchandise Store. If you switch to HTML view, you'll see that the following code has been added:

<form runat="server">

<h1>Wrox United</h1>

<h2>Official Merchandise Store</h2>

Add an HTML table of width 600 pixels to the page, with three columns and three rows. In each row, insert an ASP.NET

Image control, some text, and an ASP.NET

Button , with the following properties, and you should see the Design view as shown in Figure 11-9

Figure 11-9

Column 1: Image Control

Column 2: Text

Column 3: Button Control

Row 1

ImageUrl="images/shirt.gif"

"The Wrox United shirt, available in one size only"

id="btnBuyShirt" onclick="AddItemToBasket" width="100px"

text="Buy a shirt!" CommandArgument="Shirt"

Row 2

ImageUrl="images/hat.jpg"

"The official Wrox United hat!"

id="btnBuyHat" onclick="AddItemToBasket" width="100px"

text="Buy a hat!" CommandArgument="Hat"

Row 3

ImageUrl="images/mascot.j pg"

"The Wrox United cuddly mascot – a must-have for the younger supporters!"

id="btnBuyMascot" onclick="AddItemToBasket" width="100px"

text="Buy a Mascot!" CommandArgument="Mascot"

In HTML view, you should have the following code generated for you automatically (alternatively, instead of dragging and dropping, you could type this lot in by hand if you wanted to):

<table width="600">

<tr>

<td><asp:Image id="imgCap" runat="server" ImageUrl="images/shirt.gif">

</asp:Image></td>

<td>The Wrox United shirt, available in one size only</td>

<td><asp:Button id="btnBuyShirt" onclick="AddItemToBasket"

runat="server" Text="Buy a shirt!" Width="100px"

CommandArgument="Shirt"></asp:Button> </td>

</tr>

<tr>

<td><asp:Image id="imgShirt" runat="server" ImageUrl="images/hat.jpg">

</asp:Image></td>

<td>The official Wrox United hat!</td>

<td><asp:Button id="btnBuyHat" onclick="AddItemToBasket"

runat="server" Text="Buy the hat!" Width="100px"

CommandArgument="Hat"></asp:Button> </td>

</tr>

<tr>

<td><asp:Image id="imgMascot" runat="server"

ImageUrl="images/mascot1.jpg">

</asp:Image></td>

<td>The Wrox United cuddy mascot - a must-have for the younger

supporters!

</td>

<td><asp:Button id="btnBuyMascot" onclick="AddItemToBasket"

runat="server" Text="Buy the mascot!" Width="100px"

CommandArgument="Mascot"></asp:Button> </td>

</tr>

</table>

Add the following code below the table, while still in HTML view:

<br/>

<p>

Your basket contains:

<asp:label id="lblBasketMessage" runat="server"></asp:label>

</p>

<p>

<asp:Repeater id="basketlist" runat="server">

<itemTemplate>

<asp:Label width="70" runat="server"

text='<%# ((DictionaryEntry)Container.DataItem).Key + "s: " %>'>

</asp:Label>

&nbsp;

<asp:Label runat="server"

text='<%# ((DictionaryEntry)Container.DataItem).Value %>'>

</asp:Label>

<br />

</itemTemplate>

</asp:Repeater>

</p>

<p>

<asp:Button id="btnCheckOut" runat="server" Text="Checkout"></asp:Button>

<asp:Button id="btnEmptyBasket" onclick="btnEmptyBasket_Click"

runat="server" Text="Empty Basket"></asp:Button>

</p>

</form>

That's it for the HTML side of things! Switch to the Code view and enter the following methods:

Note

Note that the method signature for the

AddItemToBasket() function is already generated for you, so make sure you don't add another copy of this or the page won't compile properly.

void Page_Load()

{

if (Session["Basket"] == null)

{

InitializeBasket();

}

}

void btnEmptyBasket_Click(object sender, EventArgs e)

{

InitializeBasket();

}

void AddItemToBasket(object sender, EventArgs e) {

// Each time this is run, get the CommandArgument property of the button

//that fired the event, and use this to populate the Session object

System.Web.UI.WebControls.Button theButton =

(System.Web.UI.WebControls.Button)sender;

string itemName = (string)theButton.CommandArgument;

System.Collections.Hashtable basketTable =

(System.Collections.Hashtable)Session["Basket"];

// Check whether the session contains an entry for that item

// if no entry exists, create one with value 0

if (basketTable[itemName] == null)

{

basketTable[itemName] = 0; }

// Increment the counter for the selected item

int itemCount = (int)basketTable[itemName];

basketTable[itemName] = itemCount + 1;

}

void InitializeBasket()

{

System.Collections.Hashtable basketTable = new

System.Collections.Hashtable();

Session["Basket"] = basketTable;

}

void Page_Prerender()

{

System.Collections.Hashtable basketTable =

(System.Collections.Hashtable)Session["Basket"];

basketlist.DataSource = basketTable;

basketlist.DataBind();

if ((basketTable.Count) == 0)

{

lblBasketMessage.Text = "nothing - please buy something!";

}

else

{

lblBasketMessage.Text = ";

}

}

You'll notice that there are quite a few methods in here, which we'll look at in detail in just a moment; these are designed to make the application more scalable Let's see how the page works, then the purpose of these methods will become a bit clearer. It's time to run the page and try it out! You should see the screen depicted in Figure 11-10:

Figure 11-10

How It Works

This simple example uses quite a few techniques to store data about the items in the shopping basket. The data stored in the

Session object is a hashtable that stores name-value pairs. The key field for the hashtable is the name of the item added to the basket and the value is the quantity of that item. The event handlers on that page all perform different actions on the data stored in the session. Let's start by looking through the added code.

First, we added an HTML table and some simple controls to the page. Notice that the

onClick attribute of each button in the table was set to fire the same event handler –

AddItemToBasket() . Each button has a unique

CommandArgument property that describes what is added to the basket:

<table width="600">
<tr>
<td><asp:Image id="imgCap" runat="server" ImageUrl="images/shirt.gif">
</asp:Image></td>
<td>The Wrox United shirt, available in one size only</td>

<td><asp:Button id="btnBuyShirt" onclick="AddItemToBasket" runat="server" Text="Buy a shirt!" Width="100px"

CommandArgument="Shirt"></asp:Button> </td> </tr> <tr> <td><asp:Image id="imgShirt" runat="server" ImageUrl="images/hat.jpg"> </asp:Image></td> <td>The official Wrox United hat!</td>

<td><asp:Button id="btnBuyHat" onclick="AddItemToBasket" runat="server" Text="Buy the hat!" Width="100px"

CommandArgument="Hat"></asp:Button> </td> </tr> <tr> <td><asp:Image id="imgMascot" runat="server" ImageUrl="images/mascot1.jpg"> </asp:Image></td> <td>The Wrox United cuddy mascot - a must-have for the younger supporters! </td>

<td><asp:Button id="btnBuyMascot" onclick="AddItemToBasket" runat="server" Text="Buy the mascot!" Width="100px"

CommandArgument="Mascot"></asp:Button> </td> </tr> </table>

Next, we added some more controls to display the contents of the basket and to provide the option of either clearing the basket or checking out via a checkout and payment process:

<br/>
<p>
Your basket contains: 
<asp:label id="lblBasketMessage" runat="server"></asp:label>
</p>
<p>
<asp:Repeater id="basketlist" runat="server">
<itemTemplate>

The

basketlist Repeater control will be data-bound to a

Hashtable object (as we'll see in just a moment). This enables us to do some interesting data binding in this control. The first label in this control includes an interesting statement:

<asp:Label width="70" runat="server" 
text='<%# ((DictionaryEntry)Container.DataItem).Key + "s: "%>'>
</asp:Label>

Notice that the

text property has a data-binding statement in it. Here, the

Key of the current item in the Hashtable, which is used to populate this control, is displayed in this label along with some text. In this way, we take the name of each item, and we can pluralize it by adding "s" to the end of it. The colon is there to add a neat grammatical separator between the name of the item and the quantity. So we have Mascots: 3 as the displayed text, having obtained the key

Mascot from the Hashtable. Notice that we have to add a cast to this statement to tell the C# compiler that the

DataItem is of type

DictionaryEntry , because Hashtables are collections of

DictionaryEntry data types.

The rest of the code in the HTML view of the page includes another data-binding expression to obtain the quantity of the current item in the shopping basket.

            &nbsp;
<asp:Label runat="server" 
text='<%# ((DictionaryEntry)Container.DataItem).Value %>'>
</asp:Label>
<br />
</itemTemplate>
</asp:Repeater>
</p>
<p>
<asp:Button id="btnCheckOut" runat="server" Text="Checkout"></asp:Button>
<asp:Button id="btnEmptyBasket" onclick="btnEmptyBasket_Click"
runat="server" Text="Empty Basket"></asp:Button>
</p>

Note

The online payment procedures that you would associate with the Checkout button hasn't been implemented in the code – that's a bit beyond the scope of this chapter. For more information on online payments, you might want to consult www.paypal.com, one of many online payment service providers.

It's time to work through the methods in the code. They are presented in roughly the same order in which they will be processed when a page is requested by the browser (following the chain of events as they correspond to the page lifecycle).

Firstly, the

Page_Load() event handler:

Firstly, the Page_Load() event handler:
void Page_Load()
{
if (Session["Basket"] == null)
{
InitializeBasket();
}
}

Each time the page is loaded, this method will check whether the

Session object contains a basket. If it doesn't, a new empty basket is created. We'll look at the

InitializeBasket() method that does this in just a moment.

void btnEmptyBasket_Click(object sender, EventArgs e)
{
InitializeBasket();
}

The

btnEmptyBasket_Click() method also calls the

InitializeBasket() method to clear out any existing basket data. The

AddItemToBasket() method comes next, and this one is quite interesting. For starters, all the three item buttons call this method, and they each pass a

CommandArgument property.

void AddItemToBasket(object sender, EventArgs e)
{
// Each time this is run, get the CommandArgument property of the button 
that
// fired the event, and use this to populate the Session object
System.Web.UI.WebControls.Button theButton = 
(System.Web.UI.WebControls.Button)sender;
string itemName = (string)theButton.CommandArgument;

These two lines of code are all that's needed to get the string information that specifies which button the user clicked. Once you have this string (which is set to either

Shirt ,

Hat , or

Mascot ), you can use it to add data to the session.

First, create a local

Hashtable object to temporarily store the contents of the Session's basket item. This will make the code easier to understand.

Important

The

New keyword is not included in the Hashtable's declaration – this is very important, as you'll see in a moment!

System.Collections.Hashtable basketTable =
(System.Collections.Hashtable)Session["Basket"];

The remainder of the code for this method uses

basketTable , which is the hashtable representation of the contents of the

Session object's

Basket item, to add items to the session. Now, the fun thing is that because you didn't add a

New keyword to the

basketTable , you don't get a new

Hashtable object. Instead, you refer to an existing Hashtable object, specifically the one that is stored in the

Session object. This is an example of working with reference types. Because you are working with the contents of the Session's

Basket item via the

basketTable Hashtable; anything you do to the

basketTable will affect the contents of the Session.

Note

A full discussion of value and reference types is beyond this chapter. For more information, you should read Professional ASP.NET 1.1, Wrox Press, ISBN: 0-7645-5890-0.

We check to see if the item added to the basket has been added before:

// Check whether the session contains an entry for that item
// if no entry exists, create one with value 0
if (basketTable[itemName] == null)
{
basketTable[itemName] = 0;
}

If the item has never been in the basket before, it is added to the basket and initialized to

0 to make it ready to receive a new quantity. In the next piece of code, one more of the selected item is added to the basket. Thus, if there were already three Mascots in the basket, clicking this button will add another one to the basket, resulting in a basket that contains four Mascots.However, if this were the first mascot you bought, you would end up with one mascot.

   // Increment the counter for the selected item
int itemCount = (int)basketTable[itemName];
basketTable[itemName] = itemCount + 1;
}

Let's look at the

InitializeBasket() method mentioned earlier:

void InitializeBasket()
{
System.Collections.Hashtable basketTable = new
System.Collections.Hashtable();
Session["Basket"] = basketTable;
}

In just two lines of code, a new

Hashtable object is created and the Session's

Basket item is set to point to it. The new object doesn't have any items in it, so it will be an 'empty' basket.

If this method was called in response to clicking the Empty Basket button, it's likely that you originally had a full basket, so where do all the original contents of the basket go?

Well, the answer is that by changing the Hashtable that the

Basket item is pointing to, we change which Hashtable is referenced by the Session. Thus, the old Hashtable (the old basket) is no longer pointed to by anything, and the .NET garbage collector sweeps the Hashtable away. The Hashtable is no longer wanted because no one is using it, so we can get rid of it to clear out some memory. New objects can now use the memory that was used by the old hashtable. The garbage collector is very efficient and gets rid of unreferenced objects on a regular basis. The memory that the old

Hashtable object was taking up is recycled, which means that you are less likely to run out of memory.

There is one last event handler to look at. This one handles the

Prerender() event of the page. This event is always fired when a page is loaded and is your last chance to change anything just before the page is displayed:

void Page_Prerender()
{
System.Collections.Hashtable basketTable = 
(System.Collections.Hashtable)Session["Basket"];
basketlist.DataSource = basketTable;
basketlist.DataBind();

This code uses the same Hashtable that is stored in the

Session object's

Basket item by pointing another Hashtable towards the same data. This data is stored in your computer's memory, so what you're doing is telling your program where to find that data. Again, any changes made to the

basketTable will change the contents of the

Basket (because you are changing the same object!) The

basketList control is a simple

Repeater control, like the ones used in the previous chapter. In this example, we bind the contents of the Hashtable to the

Repeater to display the contents of the basket on the page:

   if ((basketTable.Count) == 0)
{
lblBasketMessage.Text = "nothing - please buy something!";
}
else
{
lblBasketMessage.Text = ";
}
}

Lastly, a message is displayed if the basket is empty. If the count of all the items in the

basketTable Hashtable is

0 , the Hashtable or the virtual basket is empty. All this is done by the

Prerender() event handler to ensure that only the most recent data is displayed after any items that may have been added.

Remember that when you click a button, a postback is initiated and the page is reloaded. If we'd have displayed the contents of the basket by adding code to the

Page_Load() event handler, we would not be displaying the most current data, but the data of what happened that last time the button was clicked, because the

Page_Load() event handler will run before the button click event is handled. You can easily try this out for yourself – if you move all the contents of this method into the

Page_Load() event handler, you'll see that the count of the number of items in the basket is one behind the actual count.

Important

The actual order of events is:

Page_Load() --> Control Events -->

Prerender()

That's it for now – Sessions will be back later when we put together a fun example that adds some style to the Wrox United site. Let's move on to applications.