Building ASP.NET Applications

Developers who are new to programming for the web always have difficulty understanding the problem of maintaining state. The HTTP protocol, the fundamental protocol of the World Wide Web, is a stateless protocol. What this means is that from a web server’s perspective, every request is from a new user. The HTTP protocol does not provide you with any method of determining whether any two requests are made by the same person.

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

No comments:

Post a Comment