However,
maintaining state is important in just about any web application. The
paradigmatic example is a shopping cart. If you want to associate a
shopping cart with a user over multiple page requests, then you need
some method of maintaining state. This chapter looks at three methods
included in the ASP.NET 3.5 Framework for associating data with a
particular user over multiple page requests. In the first section, you
learn how to create and manipulate browser cookies. A browser cookie
enables you to associate a little bit of text with each website user.
Using Browser Cookies
Cookies
were introduced into the world with the first version of the Netscape
browser. The developers at Netscape invented cookies to solve a problem
that plagued the Internet at the time. There was no way to make money
because there was no way to create a shopping cart.
You can read Netscape’s original cookie specification at http://home.netscape.com/
newsref/std/cookie_spec.html.
Here’s how
cookies work. When a web server creates a cookie, an additional HTTP
header is sent to the browser when a page is served to the browser. The
HTTP header looks like this:
Set-Cookie: message=Hello
This Set-Cookie header causes the browser to create a cookie named message that has the value Hello.
After
a cookie has been created on a browser, whenever the browser requests a
page from the same application in the future, the browser sends a
header that looks like this:
Cookie: message=Hello
The
Cookie header contains all the cookies that have been set by the web
server. The cookies are sent back to the web server each time a request
is made from the browser. Notice that a cookie is nothing more than a
little bit of text. You can store only string values when using a
cookie. You actually can create two types of cookies: session cookies
and persistent cookies. A session cookie exists only in memory. If a
user closes the web browser, the session cookie disappears forever. A
persistent cookie, on the other hand, can last for months or even years.
When you create a persistent cookie, the cookie is stored permanently
by the user’s browser on the user’s computer. Internet Explorer, for
example, stores cookies in a set of text files contained in the
following folder:
\Documents and Settings\[user]\Cookies
The Mozilla Firefox browser, on the other hand, stores cookies in the following file:
\DocumentsandSettings\[user]\ApplicationData\Mozilla\Firefox\Profiles\[randomfolder name]\Cookies.txt
Because
different browsers store cookies in different locations, cookies are
browser-relative. If you request a page that creates a cookie when using
Internet Explorer, the cookie doesn’t exist when you open Firefox or
Opera. Furthermore, notice that both Internet Explorer and Firefox store
cookies in clear text. You should never store sensitive
information—such as social security numbers or credit card numbers—in a
cookie. Where does the name cookie come from? According to the original
Netscape cookie specification, the term cookie was selected “for no
compelling reason.” However, the name most likely derives from the UNIX
world in which a “magic cookie” is an opaque token passed between
programs.
Cookie Security Restrictions
Cookies
raise security concerns. When you create a persistent cookie, you are
modifying a file on a visitor’s computer. There are people who sit
around all day dreaming up evil things that they can do to your
computer. To prevent cookies from doing horrible things to people’s
computers, browsers enforce a number of security restrictions on
cookies. First, all cookies are domain-relative. If the Amazon website
sets a cookie, then the Barnes and Noble website cannot read the cookie.
When a browser creates a cookie, the browser records the domain
associated with the cookie and doesn’t send the cookie to another
domain. An image contained in a web page might be served from another
domain than the web page itself. Therefore, when the browser makes a
request for the image, a cookie can be set from the other domain.
Companies, such as DoubleClick, that display and track advertisements on
multiple websites take advantage of this loophole to track
advertisement statistics across multiple websites. This type of cookie
is called a thirdparty cookie.
The other
important restriction that browsers place on cookies is a restriction on
size. A single domain cannot store more than 4096 bytes. This size
restriction encompasses the size of both the cookie names and the cookie
values.
Creating Cookies
You
create a new cookie by adding a cookie to the Response.Cookies
collection. The Response.Cookies collection contains all the cookies
sent from the web server to the web browser.
SetCookie.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
protected void btnAdd_Click(object sender, EventArgs e)
{
Response.Cookies[“message”].Value = txtCookieValue.Text;
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Set Cookie</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
<asp:Label id=”lblCookieValue” Text=”Cookie Value:” AssociatedControlID=”txtCookieValue”
Runat=”server” />
<asp:TextBox id=”txtCookieValue” Runat=”server” />
<asp:Button id=”btnAdd” Text=”Add Value” OnClick=”btnAdd_Click” Runat=”server” />
</div>
</form>
</body>
</html>
Be warned
that cookie names are case sensitive. Setting a cookie named message is
different from setting a cookie named Message. If you want to modify the
value of the cookie created by the page in Listing, then you can open
the page and enter a new value for the message cookie. When the web
server sends its response to the browser, the modified value of the
cookie is set on the browser. The page in Listing creates a session
cookie. The cookie disappears when you close your web browser. If you
want to create a persistent cookie, then you need to specify an
expiration date for the cookie. The page in Listing creates a persistent
cookie.
SetPersistentCookie.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_Load()
{
// Get current value of cookie
int counter = 0;
if (Request.Cookies[“counter”] != null)
counter = Int32.Parse(Request.Cookies[“counter”].Value);
// Increment counter
counter++;
// Add persistent cookie to browser
Response.Cookies[“counter”].Value = counter.ToString();
Response.Cookies[“counter”].Expires = DateTime.Now.AddYears(2);
// Display value of counter cookie
lblCounter.Text = counter.ToString();
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Set Persistent Cookie</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
You have visited this page
<asp:Label id=”lblCounter” Runat=”server” />
times!
</div>
</form>
</body>
</html>
The page in
Listing tracks the number of times that you have requested the page. A
persistent cookie named counter is used to track page requests. Notice
that the counter cookie’s Expires property is set to two years in the
future. When you set a particular expiration date for a cookie, the
cookie is stored as a persistent cookie.
Reading Cookies
You
use the Response.Cookies collection to create and modify cookies. You
use the Request.Cookies collection to retrieve a cookie’s value. For
example, the page in Listing retrieves the message cookie’s value.
GetCookie.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_Load()
{
if (Request.Cookies[“message”] != null)
lblCookieValue.Text = Request.Cookies[“message”].Value;
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Get Cookie</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
The value of the message cookie is:
<asp:Label id=”lblCookieValue” Runat=”server” />
</div>
</form>
</body>
</html>
In Listing,
the IsNothing() function is used to check whether the cookie exists
before reading its value. If you don’t include this check, you might get
a null reference exception. Also, don’t forget that cookie names are
case-sensitive. The page in Listing lists all cookies contained in the
Request.Cookies collection
GetAllCookies.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_Load()
{
ArrayList colCookies = new ArrayList();
for (int i = 0; i < Request.Cookies.Count; i++)
colCookies.Add(Request.Cookies[i]);
grdCookies.DataSource = colCookies;
grdCookies.DataBind();
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Get All Cookies</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
<asp:GridView id=”grdCookies” Runat=”server”/>
</div>
</form>
</body>
</html>
Notice that
the only meaningful information that you get back from iterating
through the Request.Cookies collection is the HasKeys, Name, and Value
properties. The other columns show incorrect information. For example,
the Expires column always displays a minimal date. Browsers don’t
communicate these additional properties with page requests, so you can’t
retrieve these property values. When using the Request.Cookies
collection, it is important to understand that a For...Each loop returns
different values than a For...Next loop. If you iterate through the
Request.Cookies collection with a For...Each loop, you get the cookie
names. If you iterate through the collection with a For...Next loop,
then you get instances of the HttpCookie class (described in the next
section).
Setting Cookie Properties
Cookies
are represented with the HttpCookie class. When you create or read a
cookie, you can use any of the properties of this class:
Domain—Enables you to specify the domain associated with the cookie. The default value is the current domain.
Expires—Enables you to create a persistent cookie by specifying an expiration date.
HasKeys—Enables you to determine whether a cookie is a multivalued cookie.
HttpOnly—Enables you to prevent a cookie from being accessed by JavaScript.
Name—Enables you to specify a name for a cookie.
Path—Enables you to specify the path associated with a cookie. The default value is /.
Secure—Enables you to require a cookie to be transmitted across a Secure Sockets Layer (SSL) connection.
Value—Enables you to get or set a cookie value.
Values—Enables you to get or set a particular value when working with a multivalued.
Deleting Cookies
The
method for deleting cookies is not intuitive. To delete an existing
cookie, you must set its expiration date to a date in the past. The page
in Listing illustrates how you can delete a single cookie. The page
contains a form field for the cookie name. When you submit the form, the
cookie with the specified name is deleted.
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
protected void btnDelete_Click(object sender, EventArgs e)
{
Response.Cookies[txtCookieName.Text].Expires = DateTime.Now.AddDays(-1);
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Delete Cookie</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
<asp:Label id=”lblCookieName” Text=”Cookie Name:” AssociatedControlID=”txtCookieName”
Runat=”server” />
<asp:TextBox id=”txtCookieName” Runat=”server” />
<asp:Button id=”btnDelete” Text=”Delete Cookie” OnClick=”btnDelete_Click”
Runat=”server” />
</div>
</form>
</body>
</html>
The
particular date that you specify when deleting a cookie doesn’t really
matter as long as it is in the past. In Listing, the expiration date is
set to 1 day ago. The page in Listing deletes all cookies sent from the
browser to the current domain (and path).
DeleteAllCookies.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_Load()
{
string[] cookies = Request.Cookies.AllKeys;
foreach (string cookie in cookies)
{
BulletedList1.Items.Add(“Deleting “ + cookie);
Response.Cookies[cookie].Expires = DateTime.Now.AddDays(-1);
}
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Delete All Cookies</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
<h1>Delete All Cookies</h1>
<asp:BulletedList id=”BulletedList1” EnableViewState=”false” Runat=”server” />
</div>
</form>
</body>
</html>
The page in Listing loops through all the cookie names from the Request.Cookies collection and deletes each cookie.
Working with Multivalued Cookies
According
to the cookie specifications, browsers should not store more than 20
cookies from a single domain. You can work around this limitation by
creating multivalued cookies. A multivalued cookie is a single cookie
that contains subkeys. You can create as many subkeys as you need. For
example, the page in Listing creates a multivalued cookie named
preferences. The preferences cookie is used to store a first name, last
name, and favorite color.
SetCookieValues.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void btnSubmit_Click(Object s, EventArgs e)
{
Response.Cookies[“preferences”][“firstName”] = txtFirstName.Text;
Response.Cookies[“preferences”][“lastName”] = txtLastName.Text;
Response.Cookies[“preferences”][“favoriteColor”] = txtFavoriteColor.Text;
Response.Cookies[“preferences”].Expires = DateTime.MaxValue;
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Set Cookie Values</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
<asp:Label id=”lblFirstName” Text=”First Name:” AssociatedControlID=”txtFirstName”
Runat=”server” />
<br />
<asp:TextBox id=”txtFirstName” Runat=”server” />
<br /><br />
<asp:Label id=”lblLastName” Text=”Last Name:” AssociatedControlID=”txtFirstName”
Runat=”server” />
<br />
<asp:TextBox id=”txtLastName” Runat=”server” />
<br /><br />
<asp:Label id=”lblFavoriteColor” Text=”Favorite Color:” AssociatedControlID=”txtFavoriteColor”
Runat=”server” />
<br />
<asp:TextBox id=”txtFavoriteColor” Runat=”server” />
<br /><br />
<asp:Button id=”btnSubmit” Text=”Submit” OnClick=”btnSubmit_Click”
Runat=”server” />
</div>
</form>
</body>
</html>
The page in Listing reads the values from the preferences cookie.
GetCookieValues.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_Load()
{
if (Request.Cookies[“preferences”] != null)
{
lblFirstName.Text = Request.Cookies[“preferences”][“firstName”];
lblLastName.Text = Request.Cookies[“preferences”][“lastName”];
lblFavoriteColor.Text = Request.Cookies[“preferences”][“favoriteColor”];
}
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Get Cookie Values</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
First Name:
<asp:Label id=”lblFirstName” Runat=”server” />
<br />
Last Name:
<asp:Label id=”lblLastName” Runat=”server” />
<br />
Favorite Color:
<asp:Label id=”lblFavoriteColor” Runat=”server” />
</div>
</form>
</body>
</html>
You can use the HttpCookie.HasKeys property to detect whether a cookie is a normal cookie or a multivalued cookie.
Using Session State
You can’t
really use a cookie to store a shopping cart. A cookie is just too small
and too simple. To enable you to work around the limitations of
cookies, the ASP.NET Framework supports a feature called Session state.
Like cookies, items stored in Session state are scoped to a particular
user. You can use Session state to store user preferences or other
user-specific data across multiple page requests. Unlike cookies,
Session state has no size limitations. If you had a compelling need, you
could store gigabytes of data in Session state. Furthermore, unlike
cookies, Session state can represent more complex objects than simple
strings of text. You can store any object in Session state. For example,
you can store a DataSet or a custom shopping cart object in Session
state. You add items to Session state by using the Session object. For
example, the page in Listing adds a new item named message to Session
state that has the value Hello World!.
SessionSet.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_Load()
{
Session[“message”] = “Hello World!”;
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Session Set</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
<h1>Session item added!</h1>
</div>
</form>
</body>
</html>
In the
Page_Load() event handler in Listing a new item is added to the Session
object. Notice that you can use the Session object just as you would use
a Hashtable collection. The page in Listing illustrates how you can
retrieve the value of an item that you have stored in Session state.
SessionGet.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_Load()
{
lblMessage.Text = Session[“message”].ToString();
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Session Get</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
<asp:Label id=”lblMessage” Runat=”server” />
</div>
</form>
</body>
</html>
When you
use Session state, a session cookie named ASP.NET_SessionId is added to
your browser automatically. This cookie contains a unique identifier. It
is used to track you as you move from page to page. When you add items
to the Session object, the items are stored on the web server and not
the web browser. The ASP.NET_SessionId cookie is used to associate the
correct data with the correct user.
By
default, if cookies are disabled, Session state does not work. You
don’t receive an error, but items that you add to Session state aren’t
available when you attempt to retrieve them in later page requests. (You
learn how to enable cookieless Session state later in this section.)
Be careful
not to abuse Session state by overusing it. A separate copy of each item
added to Session state is created for each user who requests the page.
If you place a DataSet with 400 records into Session state in a page,
and 500 users request the page, then you’ll have 500 copies of that
DataSet in memory. By default, the ASP.NET Framework assumes that a user
has left the website when the user has not requested a page for more
than 20 minutes. At that point, any data stored in Session state for the
user is discarded.
Storing Database Data in Session State
You
can use Session state to create a user-relative cache. For example, you
can load data for a user and enable the user to sort or filter the
data.
SessionDataView.aspx
<%@ Page Language=”C#” %>
<%@ Import Namespace=”System.Data” %>
<%@ Import Namespace=”System.Data.SqlClient” %>
<%@ Import Namespace=”System.Web.Configuration” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
DataView dvMovies;
/// <summary>
/// Load the Movies
/// </summary>
void Page_Load()
{
dvMovies = (DataView)Session[“Movies”];
if (dvMovies == null)
{
string conString = WebConfigurationManager.ConnectionStrings[“Movies”].
➥ConnectionString;
SqlDataAdapter dad = new SqlDataAdapter(“SELECT Id,Title,Director FROM
➥Movies”, conString);
DataTable dtblMovies = new DataTable();
dad.Fill(dtblMovies);
dvMovies = new DataView(dtblMovies);
Session[“Movies”] = dvMovies;
}
}
/// <summary>
/// Sort the Movies
/// </summary>
protected void grdMovies_Sorting(object sender, GridViewSortEventArgs e)
{
dvMovies.Sort = e.SortExpression;
}
/// <summary>
/// Render the Movies
/// </summary>
void Page_PreRender()
{
grdMovies.DataSource = dvMovies;
grdMovies.DataBind();
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Session DataView</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
<asp:GridView id=”grdMovies” AllowSorting=”true” EnableViewState=”false”
OnSorting=”grdMovies_Sorting” Runat=”server” />
<br />
<asp:LinkButton id=”lnkReload” Text=”Reload Page” Runat=”server” />
</div>
</form>
</body>
</html>
In Listing,
a DataView object is stored in Session state. When you sort the
GridView control, the DataView is sorted. The page in Listing includes a
link that enables you to reload the page. Notice that
the
sort order of the records displayed by the GridView is remembered
across page requests. The sort order is remembered even if you navigate
to another page before returning to the page.
Using the Session Object
The
main application programming interface for working with Session state
is the HttpSessionState class. This object is exposed by the
Page.Session, Context.Session, UserControl.Session, WebService.Session,
and Application.Session properties. This means that you can access
Session state from just about anywhere.This HttpSessionState class
supports the following properties (this is not a complete list):
CookieMode—Enables
you to specify whether cookieless sessions are enabled. Possible values
are AutoDetect, UseCookies, UseDeviceProfile, and UseUri.
Count—Enables you to retrieve the number of items in Session state.
IsCookieless—Enables you to determine whether cookieless sessions are enabled.
IsNewSession—Enables you to determine whether a new user session was created with the current request.
IsReadOnly—Enables you to determine whether the Session state is read-only.
Keys—Enables you to retrieve a list of item names stored in Session state.
Mode—Enables
you to determine the current Session state store provider. Possible
values are Custom, InProc, Off, SqlServer, and StateServer.
SessionID—Enables you to retrieve the unique session identifier.
Timeout—Enables
you to specify the amount of time in minutes before the web server
assumes that the user has left and discards the session. The maximum
value is 525,600 (1 year).
The HttpSessionState object also supports the following methods:
Abandon—Enables you to end a user session.
Clear—Enables you to clear all items from Session state.
Remove—Enables you to remove a particular item from Session state.
The
Abandon() method enables you to end a user session programmatically. For
example, you might want to end a user session automatically when a user
logs out from your application to clear away all of a user’s session
state information.
Handling Session Events
There
are two events related to Session state that you can handle in the
Global.asax file: the Session Start and Session End events. The Session
Start event is raised whenever a new user session begins. You can handle
this event to load user information from the database. For example, you
can handle the Session Start event to load the user’s shopping cart.
The Session End event is raised when a session ends. A session comes to
an end when it times out because of user inactivity or when it is
explicitly ended with the Session.Abandon() method. You can handle the
Session End event, for example, when you want to automatically save the
user’s shopping cart to a database table. The Global.asax file in
Listing demonstrates how you can handle both the Session Start and End
events.
Global.asax
<%@ Application Language=”C#” %>
<script runat=”server”>
void Application_Start(object sender, EventArgs e)
{
Application[“SessionCount”] = 0;
}
void Session_Start(object sender, EventArgs e)
{
Application.Lock();
int count = (int)Application[“SessionCount”];
Application[“SessionCount”] = count + 1;
Application.UnLock();
}
void Session_End(object sender, EventArgs e)
{
Application.Lock();
int count = (int)Application[“SessionCount”];
Application[“SessionCount”] = count - 1;
Application.UnLock();
}
</script>
ShowSessionCount.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_Load()
{
lblSessionCount.Text = Application[“SessionCount”].ToString();
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Show Session Count</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
The page in Listing displays the number of active sessions with a Label
Total Application Sessions:
<asp:Label id=”lblSessionCount” Runat=”server” />
</div>
</form>
</body>
</html>
The Session
End event is not raised by all session store providers. The event is
raised by the InProc session store provider (the default provider), but
it is not raised by the StateServer or SQLServer state providers.
Controlling When a Session Times Out
By
default, the ASP.NET Framework assumes that a user has left an
application after 20 minutes have passed without the user requesting a
page. In some situations, you’ll want to modify the default timeout
value. For example, imagine that you are creating a college admissions
website and the website includes a form that enables an applicant to
enter a long essay. In that situation, you would not want the user
session to timeout after 20 minutes. Please, give the poor college
applicants at least 1 hour to write their essays. The disadvantage of
increasing the Session timeout is that more memory is consumed by your
application. The longer the Session timeout, the more server memory is
potentially consumed.
You can
specify the Session timeout in the web configuration file or you can set
the Session timeout programmatically. For example, the web
configuration file in Listing changes the Session timeout value to 60 (1
hour).
Web.Config
<configuration>
<system.web>
<sessionState timeout=”60” />
</system.web>
</configuration>
You can
modify the Session timeout value programmatically with the Timeout
property of the Session object. For example, the following statement
changes the timeout value from the default of 20 minutes to 60 minutes.
Session.Timeout = 60
After you
execute this statement, the timeout value is modified for the remainder
of the user session. This is true even when the user visits other pages.
Using Cookieless Session State
By
default, Session state depends on cookies. The ASP.NET Framework uses
the ASP.NET_SessionId cookie to identity a user across page requests so
that the correct data can be associated with the correct user. If a user
disables cookies in the browser, then Session state doesn’t work. If
you want Session state to work even when cookies are disabled, then you
can take advantage of cookieless sessions. When cookieless sessions are
enabled, a user’s session ID is added to the page URL.Here’s a sample
of what a page URL looks like when cookieless sessions are enabled:
The
strange-looking code in this URL is the current user’s Session ID. It is
the same value as the one you get from the Session.SessionID property.
You enable cookieless sessions by modifying the sessionState element in
the web configuration file. The sessionState element includes a
cookieless attribute that accepts the following values:
AutoDetect—The Session ID is stored in a cookie when a browser has cookies enabled. Otherwise, the cookie is added to the URL.
UseCookies—The Session ID is always stored in a cookie (the default value).
UseDeviceProfile—The Session ID is stored in a cookie when a browser supports cookies. Otherwise, the cookie is added to the URL.
UseUri—The Session ID is always added to the URL.
When you
set cookieless to the value UseDeviceProfile, the ASP.NET Framework
determines whether the browser supports cookies by looking up the
browser’s capabilities from a set of files contained in the following
folder:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG\Browsers
If,
according to these files, a browser supports cookies, then the ASP.NET
Framework uses a cookie to store the Session ID. The Framework attempts
to add a cookie even when a user has disabled cookies in the browser.
When cookieless is set to the value AutoDetect, the framework checks for
the existence of the HTTP Cookie header. If the Cookie header is
detected, then the framework stores the Session ID in a cookie.
Otherwise, the framework falls back to storing the Session ID in the
page URL. The web configuration file in Listing enables cookieless
sessions by assigning the value AutoDetect to the cookieless attribute.
Web.Config
<configuration>
<system.web>
<sessionState cookieless=”AutoDetect” regenerateExpiredSessionId=”true” />
</system.web>
</configuration>
The easiest
way to test cookieless sessions is to use the Mozilla Firefox browser
because this browser enables you to disable cookies easily. Select the
menu option Tools, Options. Select the Privacy tab and uncheck Allow
Sites to Set Cookies.
Configuring State Server Session State
When
you enable State Server Session state, Session state information is
stored in a separate Windows NT Service. The Windows NT Service can be
located on the same server as your web server, or it can be located on
another server in your network. If you store Session state in the memory
of a separate Windows NT Service, then Session state information
survives even when your ASP.NET application doesn’t. For example, if
your ASP.NET application crashes, then your Session state information is
not lost because it is stored in a separate process. Furthermore, you
can create a web farm when you store state information by using a
Windows NT Service. You can designate one server in your network as your
state server. All the web servers in your web farm can use the central
state server to store Session state. You must complete the following two
steps to use State Server Session state:
. Start the ASP.NET State Service.
. Configure your application to use the ASP.NET State Service.
You can
start the ASP.NET State Service by opening the Services applet located
at Start, Control Panel, Administrative Tools (see Figure 24.6). After
you open the Services applet, double-click the ASP.NET State Service and
click Start to run the service. You also should change the Startup type
of the service to the value Automatic so that the service starts
automatically every time that you reboot your machine.
If you want
to run the ASP.NET State Service on a separate server on your network,
then you must edit a Registry setting on the server that hosts the
ASP.NET State Service. By default, the ASP.NET State Service does not
accept remote connections. To allow remote connections, execute RegEdit
from a command prompt and set the following Registry key to the value 1:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\
AllowRemoteConnection
After you
start the ASP.NET State Service, you need to configure your ASP.NET
application to use it. The web configuration file in Listing 24.16
enables State Server Session State. LISTING Web.Config
<configuration>
<system.web>
<sessionState mode=”StateServer” stateConnectionString=”tcpip=localhost:42424”
stateNetworkTimeout=”10” />
<machineKey decryption=”AES” validation=”SHA1”
decryptionKey=”306C1FA852AB3B0115150DD8BA30821CDFD125538A0C606D
ACA53DBB3C3E0AD2”
validationKey=”61A8E04A146AFFAB81B6AD19654F99EA7370807F18F5002725DAB98B8EFD
19C711337E26948E26D1D174B159973EA0BE8CC9CAA6AAF513BF84E44B2247792265” />
</system.web>
</configuration>
The web
configuration file in Listing modifies three attributes of the
sessionState element. First, the mode attribute is set to the value
StateServer. Next, the stateConnectionString attribute is used to
specify the location of the ASP.NET State Server. In Listing, a
connection is created to the local server on port 42424. Finally, the
stateNetworkTimeout attribute is used to specify a connection timeout in
seconds. Notice that the web configuration in Listing includes a
machineKey element. If you are setting up a web farm, and you need to
use the same State Server to store Session state for multiple servers,
then you are required to specify explicit encryption and validation
keys. On the other hand, you don’t need to include a machineKey element
when the ASP.NET State Server is hosted on the same machine as your
ASP.NET application.
You can configure the ASP.NET State Server to use a different port by modifying the following Registry value:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\
Parameters\Port
You need to
stop and restart the ASP.NET State Service with the Services applet
after making this modification.Don’t use the web configuration file in
Listing without modifying the values of both the decryptionKey and
validationKey attributes. Those values must be secret. You can use the
GenerateKeys.aspx page discussed in the previous chapter to generate new
values for these attributes. After you complete these configuration
steps, Session state information is stored in the ASP.NET State Server
automatically. You don’t need to modify any of your application code
when you switch to out-of-process Session state.
Configuring SQL Server Session State
If
you want to store Session state in the most reliable way possible, then
you can store Session state in a Microsoft SQL Server database. Because
you can set up failover SQL Server clusters, Session state stored in
SQL Server should be able to survive just anything, including a major
nuclear war. You must complete the following two steps to enable SQL
Server Session state:
. Configure your database to support SQL Server Session state.
. Configure your application to use SQL Server Session state.
You can use
the aspnet_regsql tool to add the necessary tables and stored
procedures to your database to support SQL Server Session state. The
aspnet_regsql tool is located in the following path:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regsql.exe
If you open
the SDK Command Prompt, you don’t need to navigate to the Microsoft.NET
folder to use the aspnet_regsql tool. Executing the following command
enables SQL Server Session state for a database server named YourServer.
aspnet_regsql -C “Data Source=YourServer;Integrated Security=True” -ssadd
When you
execute this command, a new database is created on your database server
named ASPState. The ASPState database contains all the stored procedures
used by Session state. However, by default, Session state information
is stored in the TempDB database. When your database server restarts,
the TempDB database is cleared automatically. If you want to use SQL
Server Session state with a failover cluster of SQL Servers, then you
can’t store Session state in the TempDB database. Also, if you want
Session state to survive database restarts, then you can’t store the
state information in the TempDB database. If you execute the following
command, then Session state is stored in the ASPState database
instead of the TempDB database:
aspnet_regsql -C “Data Source=YourServer;Integrated Security=True” -ssadd -sstype p
Notice that
this command includes a -sstype p switch. The p stands for persistent.
Session state that is stored in the ASPState database is called
persistent Session state because it survives database server restarts.
Finally, you can store Session state in a custom database. The following
command stores Session state in a database named
MySessionDB: aspnet_regsql -C “Data Source=YourServer;Integrated Security=True”-ssadd -sstype c
-d MySessionDB
Executing
this command creates a new database named MySessionDB that contains both
the tables and stored procedures for storing Session state. Notice that
the -sstype switch has the value c for custom. The command also
includes a -d switch that enables you to specify the name of the new
database. If you want to remove the Session state tables and stored
procedures from a server, then you can execute the following command:
aspnet_regsql -C “Data Source=YourServer;Integrated Security=True” -ssremove
Executing
this command removes the ASPState database. It does not remove a custom
Session state database. You must remove a custom database manually.
After you configure your database server to support Session state, you
must configure your ASP.NET application to connect to your database. You
can use the web configuration file in Listing to connect to a database
named YourServer.
LISTING Web.Config
<configuration>
<system.web>
<sessionState mode=”SQLServer”
sqlConnectionString=”Data Source=YourServer;Integrated Security=True” sqlCommandTimeout=”30” />
<machineKey decryption=”AES” validation=”SHA1”
decryptionKey=”306C1FA852AB3B0115150DD8BA30821CDFD125538A0C606D
➥ACA53DBB3C3E0AD2”
validationKey=”61A8E04A146AFFAB81B6AD19654F99EA7370807F18F5002725DAB98B8EF
➥D19C711337E26948E26D1D174B159973EA0BE8CC9CAA6AAF513BF84E44B2247792265” />
</system.web>
</configuration>
The
sessionState element includes three attributes. The mode attribute is
set to the value SQLServer to enable SQL Server Session state. The
second attribute, sqlConnectionString, contains the connection string to
the Session state database. Finally, the sqlCommandTimeout specifies
the maximum amount of time in seconds before a command that retrieves or
stores Session state times out. Notice that the configuration file in
Listing includes a machineKey element. If your Session state database is
located on a different machine than your ASP.NET application, then you
are required to include a machineKey element that contains explicit
encryption and validation keys.
Don’t use
the web configuration file in Listing without modifying the values of
both the decryptionKey and validationKey attributes. Those values must
be secret. You can use the GenerateKeys.aspx page discussed in the
previous chapter to generate new values for these attributes. If you
select the option to store Session state in a custom database when
executing the
aspnet_regsql
tool, then you need to specify the name of the custom database in your
configuration file. You can use the web configuration file in Listing.
LISTING Web.config
<configuration>
<system.web>
<sessionState mode=”SQLServer” sqlConnectionString=”Data Source=YourServer;
Integrated Security=True;database=MySessionDB” sqlCommandTimeout=”30”
allowCustomSqlDatabase=”true”/>
<machineKey decryption=”AES” validation=”SHA1”
decryptionKey=”306C1FA852AB3B0115150DD8BA30821CDFD125538A0C606D
➥ACA53DBB3C3E0AD2”
validationKey=”61A8E04A146AFFAB81B6AD19654F99EA7370807F18F5002725DAB98B8EFD
➥19C711337E26948E26D1D174B159973EA0BE8CC9CAA6AAF513BF84E44B2247792265” />
</system.web>
</configuration>
The
sessionState element in the configuration file in Listing includes an
allowCustomSqlDatabase attribute. Furthermore, the sqlConnectionString
attribute contains the name of the custom database. Enabling SQL Server
session state has no effect on how you write your application code. You
can initially build your application using in-process Session state and,
when you have the need, you can switch to SQL Server Session state.
Using Profiles
The
ASP.NET Framework provides you with an alternative to using cookies or
Session state to store user information: the Profile object. The Profile
object provides you with a strongly typed, persistent form of session
state. You create a Profile by defining a list of Profile properties in
your application root web configuration file. The ASP.NET Framework
dynamically compiles a class that contains these properties in the
background. For example, the web configuration file in Listing defines a
Profile that contains three properties: firstName, lastName, and
numberOfVisits.
LISTING Web.Config
<configuration>
<system.web>
<profile>
<properties>
<add name=”firstName” />
<add name=”lastName” />
<add name=”numberOfVisits” type=”Int32” defaultValue=”0” />
</properties>
</profile>
</system.web>
</configuration>
When you define a Profile property, you can use any of the following attributes:
name—Enables you to specify the name of the property.
type—Enables
you to specify the type of the property. The type can be any custom
type, including a custom component that you define in the App_Code
folder. (The default type is string.)
defaultValue—Enables you to specify a default value for the property.
readOnly—Enables you to create a read-only property. (The default value is false.)
serializeAs—Enables
you to specify how a property is persisted into a static
representation. Possible values are Binary, ProviderSpecific, String,
and Xml. (The default value is ProviderSpecific.)
allowAnonymous—Enables you to allow anonymous users to read and set the property. (The default value is false.)
provider—Enables you to associate the property with a particular Profile provider.
customProviderData—Enables you to pass custom data to a Profile provider.
After you
define a Profile in the web configuration file, you can use the Profile
object to modify the Profile properties. For example, the page in
Listing enables you to modify the firstName and lastName properties with
a form. Furthermore, the page automatically updates the numberOfVisits
property each time the page is requested.
LISTING ShowProfile.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_PreRender()
{
lblFirstname.Text = Profile.firstName;
lblLastName.Text = Profile.lastName;
Profile.numberOfVisits++;
lblNumberOfVisits.Text = Profile.numberOfVisits.ToString();
}
protected void btnUpdate_Click(object sender, EventArgs e)
{
Profile.firstName = txtNewFirstName.Text;
Profile.lastName = txtNewLastName.Text;
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Show Profile</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
First Name:
<asp:Label id=”lblFirstname” Runat=”server” />
<br /><br />
Last Name:
<asp:Label id=”lblLastName” Runat=”server” />
<br /><br />
Number of Visits:
<asp:Label id=”lblNumberOfVisits” Runat=”server” />
<hr />
<asp:Label id=”lblNewFirstName” Text=”New First Name:”
AssociatedControlID=”txtNewFirstName” Runat=”server” />
<asp:TextBox id=”txtNewFirstName” Runat=”server” />
<br /><br />
<asp:Label id=”lblNewLastName” Text=”New Last Name:”
AssociatedControlID=”txtNewLastName” Runat=”server” />
<asp:TextBox id=”txtNewLastName” Runat=”server” />
<br /><br />
<asp:Button id=”btnUpdate” Text=”Update Profile”
OnClick=”btnUpdate_Click” Runat=”server” />
</div>
</form>
</body>
</html>
Notice that
Profile properties are exposed as strongly typed properties. The
numberOfVisits property, for example, is exposed as an integer property
because you defined it as an integer property. It is important to
understand that Profile properties are persistent. If you set a Profile
property for a user, and that user does not return to your website for
500 years, the property retains its value. Unlike Session state, when
you assign a value to a Profile property, the value does not evaporate
after a user leaves your website.
Supporting Anonymous Users
By
default, anonymous users cannot modify Profile properties. The problem
is that the ASP.NET Framework has no method of associating Profile data
with a particular user unless the user is authenticated. If you want to
enable anonymous users to modify Profile properties, you must enable a
feature of the ASP.NET Framework called Anonymous Identification. When
Anonymous Identification is enabled, a unique identifier (a GUID) is
assigned to anonymous users and stored in a persistent browser cookie.
You can enable cookieless anonymous identifiers. Cookieless anonymous
identifiers work just like cookieless sessions: The anonymous identifier
is added to the page URL instead of a cookie. You enable cookieless
anonymous identifiers by setting the cookieless attribute of the
anonymousIdentification element in the web configuration file to the
value UseURI or AutoDetect. Furthermore, you must mark all Profile
properties that you want anonymous users to be able to modify with the
allowAnonymous attribute. For example, the web configuration file in
Listing enables Anonymous Identification and defines a Profile property
that can be modified by anonymous users.
LISTING Web.Config
<configuration>
<system.web>
<authentication mode=”Forms” />
<anonymousIdentification enabled=”true” />
<profile>
<properties>
<add name=”numberOfVisits” type=”Int32”
defaultValue=”0” allowAnonymous=”true” />
</properties>
</profile>
</system.web>
</configuration>
The
numberOfVisits property defined in Listing includes the allowAnonymous
attribute. Notice that the web configuration file also enables Forms
authentication. When Forms authentication is enabled, and you don’t log
in, then you are an anonymous user. The page in Listing illustrates how
you modify a Profile property when Anonymous Identification is enabled.
LISTING ShowAnonymousIdentification.aspx
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_PreRender()
{
lblUserName.Text = Profile.UserName;
lblIsAnonymous.Text = Profile.IsAnonymous.ToString();
Profile.numberOfVisits++;
lblNumberOfVisits.Text = Profile.numberOfVisits.ToString();
}
protected void btnLogin_Click(object sender, EventArgs e)
{
FormsAuthentication.SetAuthCookie(“Bob”, false);
Response.Redirect(Request.Path);
}
protected void btnLogout_Click(object sender, EventArgs e)
{
FormsAuthentication.SignOut();
Response.Redirect(Request.Path);
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Show Anonymous Identification</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
User Name:
<asp:Label id=”lblUserName” Runat=”server” />
<br />
Is Anonymous:
<asp:Label id=”lblIsAnonymous” Runat=”server” />
<br />
Number Of Visits:
<asp:Label id=”lblNumberOfVisits” Runat=”server” />
<hr />
<asp:Button id=”btnReload” Text=”Reload” Runat=”server” />
<asp:Button id=”btnLogin” Text=”Login” OnClick=”btnLogin_Click” Runat=”server” />
<asp:Button id=”btnLogout” Text=”Logout” OnClick=”btnLogout_Click” Runat=”server” />
</div>
</form>
</body>
</html>
Each time
that you request the page in Listing, the numberOfVisits Profile
property is incremented and displayed. The page includes three buttons:
Reload, Login, and Logout. The page also displays the value of the
Profile.UserName property. This property represents either the current
username or the anonymous identifier. The value of the numberOfVisits
Profile property is tied to the value of the Profile.UserName property.
You can click the Reload button to quickly reload the page and increment
the value of the numberOfVisits property.
You can
preserve the value of Profile properties when a user transitions from
anonymous to authenticated by handling the MigrateAnonymous event in the
Global.asax file. This event is raised when an anonymous user that has a
profile logs in. For example, the MigrateAnonymous event handler in
Listing automatically copies the values of all anonymous Profile
properties to the user’s current authenticated profile.
LISTING Global.asax
<%@ Application Language=”C#” %>
<script runat=”server”>
public void Profile_OnMigrateAnonymous(object sender, ProfileMigrateEventArgs
➥args)
{
// Get anonymous profile
ProfileCommon anonProfile = Profile.GetProfile(args.AnonymousID);
// Copy anonymous properties to authenticated
foreach (SettingsProperty prop in ProfileBase.Properties)
Profile[prop.Name] = anonProfile[prop.Name];
// Kill the anonymous profile
ProfileManager.DeleteProfile(args.AnonymousID);
AnonymousIdentificationModule.ClearAnonymousIdentifier();
}
</script>
The
anonymous Profile associated with the user is retrieved when the user’s
anonymous identifier is passed to the Profile.GetProfile() method. Next,
each Profile property is copied from the anonymous Profile to the
current Profile. Finally, the anonymous Profile is deleted and the
anonymous identifier is destroyed. (If you don’t destroy the anonymous
identifier, then the MigrateAnonymous event continues to be raised with
each page request after the user authenticates.)
Inheriting a Profile from a Custom Class
Instead
of defining a list of Profile properties in the web configuration file,
you can define Profile properties in a separate class. For example, the
class in Listing contains two properties named FirstName and LastName.
LISTING App_Code\SiteProfile.cs
using System;
using System.Web.Profile;
public class SiteProfile : ProfileBase
{
private string _firstName = “Your First Name”;
private string _lastName = “Your Last Name”;
[SettingsAllowAnonymous(true)]
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
[SettingsAllowAnonymous(true)]
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
}
Notice that
the class in Listing inherits from the BaseProfile class. After you
declare a class, you can use it to define a profile by inheriting the
Profile object from the class in the web configuration file. The web
configuration file in Listing uses the inherits attribute to inherit the
Profile from the SiteProfile class.
LISTING Web.Config
<configuration>
<system.web>
<anonymousIdentification enabled=”true” />
<profile inherits=”SiteProfile” />
</system.web>
</configuration>
After you
inherit a Profile in the web configuration file, you can use the Profile
in the normal way. You can set or read any of the properties that you
defined in the SiteProfile class by accessing the properties through the
Profile object. If you inherit Profile properties from a class and
define Profile properties in the web configuration file, then the two
sets of Profile properties are merged. When you define Profile
properties in a class, you can decorate the properties with the
following attributes:
SettingsAllowAnonymous—Enables you to allow anonymous users to read and set the property.
ProfileProvider—Enables you to associate the property with a particular Profile provider.
CustomProviderData—Enables you to pass custom data to a Profile provider.
Creating Complex Profile Properties
To
this point, we’ve used the Profile properties to represent simple types
such as strings and integers. You can use Profile properties to
represent more complex types such as a custom ShoppingCart class. For
example, the class in Listing represents a simple shopping cart.
LISTING App_Code\ShoppingCart.cs
using System;
using System.Collections.Generic;
using System.Web.Profile;
namespace AspNetUnleashed
{
public class ShoppingCart
{
private List<CartItem> _items = new List<CartItem>();
public List<CartItem> Items
{
get { return _items; }
}
}
public class CartItem
{
private string _name;
private decimal _price;
private string _description;
public string Name
{
get { return _name; }
set { _name = value; }
}
public decimal Price
{
get { return _price; }
set { _price = value; }
}
public string Description
{
get { return _description; }
set { _description = value; }
}
public CartItem() { }
public CartItem(string name, decimal price, string description)
{
_name = name;
_price = price;
_description = description;
}
}
}
The file in
Listing actually contains two classes: the ShoppingCart class and the
CartItem class. The ShoppingCart class exposes a collection of CartItem
objects. The web configuration file in Listing defines a Profile
property named ShoppingCart that represents the ShoppingCart class. The
type attribute is set to the fully qualified name of the ShoppingCart
class.
LISTING Web.Config
<configuration>
<system.web>
<profile>
<properties>
<add name=”ShoppingCart” type=”AspNetUnleashed.ShoppingCart” />
</properties>
</profile>
</system.web>
</configuration>
Finally,
the page in Listing uses the Profile.ShoppingCart property. The contents
of the ShoppingCart are bound and displayed in a GridView control. The
page also contains a form that enables you to add new items to the
ShoppingCart.
LISTING ShowShoppingCart.aspx
<%@ Page Language=”C#” %>
<%@ Import Namespace=”AspNetUnleashed” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<script runat=”server”>
void Page_PreRender()
{
grdShoppingCart.DataSource = Profile.ShoppingCart.Items;
grdShoppingCart.DataBind();
}
protected void btnAdd_Click(object sender, EventArgs e)
{
CartItem newItem = new CartItem(txtName.Text, decimal.Parse(txtPrice.Text),
➥txtDescription.Text);
Profile.ShoppingCart.Items.Add(newItem);
}
</script>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head id=”Head1” runat=”server”>
<title>Show ShoppingCart</title>
</head>
<body>
<form id=”form1” runat=”server”>
<div>
<asp:GridView id=”grdShoppingCart” EmptyDataText=”There are no items in your shopping cart”
Runat=”server” />
<br />
<fieldset>
<legend>Add Product</legend>
<asp:Label id=”lblName” Text=”Name:” AssociatedControlID=”txtName” Runat=”server” />
<br />
<asp:TextBox id=”txtName” Runat=”server” />
<br /><br />
<asp:Label id=”lblPrice” Text=”Price:” AssociatedControlID=”txtPrice” Runat=”server” />
<br />
<asp:TextBox id=”txtPrice” Runat=”server” />
<br /><br />
<asp:Label id=”lblDescription” Text=”Description:” AssociatedControlID=”txtDescription”
Runat=”server” />
<br />
<asp:TextBox id=”txtDescription” Runat=”server” />
<br /><br />
<asp:Button id=”btnAdd” Text=”Add To Cart” Runat=”server” OnClick=”btnAdd_Click” />
</fieldset>
</div>
</form>
</body>
</html>
If you want
to take control over how complex properties are stored, you can modify
the value of the serializeAs attribute associated with a Profile
property. The serializeAs attribute accepts the following four values:
Binary
ProviderSpecific
String
Xml