Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

1262 CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

Figure 33-6. The cache application GUI

In the Click event handler of the Add This Car button, insert the new record into the AutoLot database using the InventoryDAL type. Once the record has been inserted, call a helper function named RefreshGrid(), which will update the UI:

protected void btnAddCar_Click(object sender, EventArgs e)

{

//Update the Inventory table

//and call RefreshGrid().

InventoryDAL dal = new InventoryDAL(); dal.OpenConnection(@"Data Source=(local)\SQLEXPRESS;" +

"Initial Catalog=AutoLot;Integrated Security=True"); dal.InsertAuto(int.Parse(txtCarID.Text), txtCarColor.Text,

txtCarMake.Text, txtCarPetName.Text); dal.CloseConnection();

RefreshGrid();

}

private void RefreshGrid()

{

InventoryDAL dal = new InventoryDAL(); dal.OpenConnection(@"Data Source=(local)\SQLEXPRESS;" +

"Initial Catalog=AutoLot;Integrated Security=True");

CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

1263

DataTable theCars = dal.GetAllInventory(); dal.CloseConnection();

carsGridView.DataSource = theCars; carsGridView.DataBind();

}

Now, to test the use of the cache, begin by running the current program (Ctrl+F5) and copy the URL appearing in the browser to your clipboard. Next, launch a second instance of Internet Explorer (using the Start button) and paste the URL into this instance. At this point you should have two instances of your web browser, both viewing Default.aspx and showing identical data.

In one instance of the browser, add a new automobile entry. Obviously, this results in an updated GridView viewable from the browser that initiated the postback.

In the second browser instance, click the Refresh button (F5). You should not see the new item, given that the Page_Load event handler is reading directly from the cache. (If you did see the value, the 15 seconds had already expired. Either type faster or increase the amount of time the DataTable will remain in the cache.) Wait a few seconds and click the Refresh button from the second browser instance one more time. Now you should see the new item, given that the DataTable in the cache has expired and the CacheItemRemovedCallback delegate target method has automatically updated the cached DataTable.

As you can see, the major benefit of the Cache type is that you can ensure that when a member is removed, you have a chance to respond. In this example, you certainly could avoid using the Cache and simply have the Page_Load() event handler always read directly from the AutoLot database. Nevertheless, the point should be clear: the cache allows you to automatically refresh data using the cache mechanism.

Source Code The CacheState project is included under the Chapter 33 subdirectory.

Maintaining Session Data

So much for our examination of application-level and cached data. Next, let’s check out the role of per-user data. As mentioned earlier, a session is little more than a given user’s interaction with a web application, which is represented via a unique HttpSessionState object. To maintain stateful information for a particular user, the HttpApplication-derived type and any System.Web.UI.Page-derived types may access the Session property. The classic example of the need to maintain per-user data would be an online shopping cart. Again, if 10 people all log on to an online store, each individual will maintain a unique set of items that she (may) intend to purchase.

When a new user logs on to your web application, the .NET runtime will automatically assign the user a unique session ID, which is used to identify the user in question. Each session ID is assigned a custom instance of the HttpSessionState type to hold on to user-specific data. Inserting or retrieving session data is syntactically identical to manipulating application data, for example:

// Add/retrieve a session variable for current user.

Session["DesiredCarColor"] = "Green";

string color = (string) Session["DesiredCarColor"];

The HttpApplication-derived type allows you to intercept the beginning and end of a session via the Session_Start() and Session_End() event handlers. Within Session_Start(), you can freely create any per-user data items, while Session_End() allows you to perform any work you may need to do when the user’s session has terminated:

1264 CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

<%@ Application Language="C#" %>

...

void Session_Start(Object sender, EventArgs e)

{

// New session! Prep if required.

}

void Session_End(Object sender, EventArgs e)

{

// User logged off/timed out. Tear down if needed.

}

Like the HttpApplicationState type, HttpSessionState may hold any System.Object-derived type, including your custom classes. For example, assume you have a new web application (SessionState) that defines a class named UserShoppingCart:

public class UserShoppingCart

{

public string desiredCar; public string desiredCarColor; public float downPayment; public bool isLeasing;

public DateTime dateOfPickUp;

public override string ToString()

{

return string.Format

("Car: {0}<br>Color: {1}<br>$ Down: {2}<br>Lease: {3}<br>Pick-up Date: {4}", desiredCar, desiredCarColor, downPayment, isLeasing, dateOfPickUp.ToShortDateString());

}

}

Within the Session_Start() event handler, you can now assign each user a new instance of the

UserShoppingCart class:

void Session_Start(Object sender, EventArgs e)

{

Session["UserShoppingCartInfo"] = new UserShoppingCart();

}

As the user traverses your web pages, you are able to pluck out the UserShoppingCart instance and fill the fields with user-specific data. For example, assume you have a simple *.aspx page that defines a set of input widgets that correspond to each field of the UserShoppingCart type and a Button used to set the values and two Labels that will be used to display the user’s session ID and session information (see Figure 33-7).

CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

1265

Figure 33-7. The session application GUI

The server-side Click event handler for the Button type is straightforward (scrape out values from TextBoxes and display the shopping cart data on a Label type):

protected void btnSubmit_Click(object sender, EventArgs e)

{

// Set current user prefs.

UserShoppingCart cart = (UserShoppingCart)Session["UserShoppingCartInfo"];

cart.dateOfPickUp = myCalendar.SelectedDate; cart.desiredCar = txtCarMake.Text; cart.desiredCarColor = txtCarColor.Text; cart.downPayment = float.Parse(txtDownPayment.Text); cart.isLeasing = chkIsLeasing.Checked; lblUserInfo.Text = cart.ToString(); Session["UserShoppingCartInfo"] = cart;

}

Within Session_End(), you may wish to persist the fields of the UserShoppingCart to a database or whatnot (however, as you will see at the conclusion of this chapter, the ASP.NET Profile API will do so automatically). As well, you may wish to implement Session_Error() to trap any faulty input (or perhaps make use of various validation controls on the Default.aspx page to account for such user errors).

1266 CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

In any case, if you were to launch two or three instances of your browser of choice all posting to the same URL (via a copy/paste operation as you did for the data cache example), you would find that each user is able to build a custom shopping cart that maps to his unique instance of

HttpSessionState.

Additional Members of HttpSessionState

The HttpSessionState class defines a number of other members of interest beyond the type indexer. First, the SessionID property will return the current user’s unique ID. If you wish to view the automatically assigned session ID for this example, handle the Load event of your page as follows:

protected void Page_Load(object sender, EventArgs e)

{

lblUserID.Text = string.Format("Here is your ID: {0}", Session.SessionID);

}

The Remove() and RemoveAll() methods may be used to clear items out of the user’s instance of

HttpSessionState:

Session.Remove["SomeItemWeDontNeedAnymore"];

The HttpSessionState type also defines a set of members that control the expiration policy of the current session. Again, by default each user has 20 minutes of inactivity before the HttpSessionState object is destroyed. Thus, if a user enters your web application (and therefore obtains a unique session ID), but does not return to the site within 20 minutes, the runtime assumes the user is no longer interested and destroys all session data for that user. You are free to change this default 20-minute expiration value on a user-by-user basis using the Timeout property. The most common place to do so is within the scope of your Session_Start() method:

void Session_Start(Object sender, EventArgs e)

{

// Each user has 5 minutes of inactivity.

Session.Timeout = 5; Session["UserShoppingCartInfo"]

= new UserShoppingCart();

}

Note If you do not need to tweak each user’s Timeout value, you are able to alter the 20-minute default for all users via the Timeout attribute of the <sessionState> element within the Web.config file (examined at the end of this chapter).

The benefit of the Timeout property is that you have the ability to assign specific timeout values discretely for each user. For example, imagine you have created a web application that allows users to pay cash for a given membership level. You may say that Gold members should time out within one hour, while Wood members should get only 30 seconds. This possibility begs the question, how can you remember user-specific information (such as the current membership level) across web visits? One possible answer is through the user of the HttpCookie type. (And speaking of cookies . . .)

Source Code The SessionState project is included under the Chapter 33 subdirectory.

CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

1267

Understanding Cookies

The next state management technique examined here is the act of persisting data within a cookie, which is often realized as a text file (or set of files) on the user’s machine. When a user logs on to a given site, the browser checks to see whether the user’s machine has a cookie file for the URL in question and, if so, appends this data to the HTTP request.

The receiving server-side web page could then read the cookie data to create a GUI that may be based on the current user preferences. I’m sure you’ve noticed that when you visit one of your favorite websites, it somehow “just knows” the sort of content you wish to see. The reason (in part) may have to do with a cookie stored on your computer that contains information relevant to a given website.

Note The exact location of your cookie files will depend on which browser and operating system you happen to be using.

The contents of a given cookie file will obviously vary among URLs, but keep in mind that they are ultimately text files. Thus, cookies are a horrible choice when you wish to maintain sensitive information about the current user (such as a credit card number, password, or whatnot). Even if you take the time to encrypt the data, a crafty hacker could decrypt the value and use it for purely evil pursuits. In any case, cookies do play a role in the development of web applications, so let’s check out how ASP.NET handles this particular state management technique.

Creating Cookies

First of all, understand that ASP.NET cookies can be configured to be either persistent or temporary. A persistent cookie is typically regarded as the classic definition of cookie data, in that the set of name/value pairs is physically saved to the user’s hard drive. A temporary cookie (also termed a session cookie) contains the same data as a persistent cookie, but the name/value pairs are never saved to the user’s hard drive; rather, they exist only within the HTTP header. Once the user shuts down the browser, all data contained within the session cookie is destroyed.

Note Most browsers support cookies of up to 4,096 bytes. Because of this size limit, cookies are best used to store small amounts of data, such as a user ID that can be used to identify the user and pull details from a database.

The System.Web.HttpCookie type is the class that represents the server side of the cookie data (persistent or temporary). When you wish to create a new cookie, you access the Response.Cookies property. Once the new HttpCookie is inserted into the internal collection, the name/value pairs flow back to the browser within the HTTP header.

To check out cookie behavior firsthand, create a new ASP.NET web application (CookieStateApp) and create the UI displayed in Figure 33-8.

1268 CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

Figure 33-8. The UI of CookieStateApp

Within the first button’s Click event handler, build a new HttpCookie and insert it into the Cookie collection exposed from the HttpRequest.Cookies property. Be very aware that the data will not persist itself to the user’s hard drive unless you explicitly set an expiration date using the HttpCookie.Expires property. Thus, the following implementation will create a temporary cookie that is destroyed when the user shuts down the browser:

protected void btnCookie_Click(object sender, EventArgs e)

{

// Make a temp cookie.

HttpCookie theCookie =

new HttpCookie(txtCookieName.Text, txtCookieValue.Text);

Response.Cookies.Add(theCookie);

}

However, the following generates a persistent cookie that will expire on March 24, 2009:

protected void btnCookie_Click(object sender, EventArgs e)

{

HttpCookie theCookie =

new HttpCookie(txtCookieName.Text, txtCookieValue.Text);

theCookie.Expires = DateTime.Parse("03/24/2009");

Response.Cookies.Add(theCookie);

}

Reading Incoming Cookie Data

Recall that the browser is the entity in charge of accessing persisted cookies when navigating to a previously visited page. To interact with the incoming cookie data under ASP.NET, access the HttpRequest.Cookies property. To illustrate, implement the Click event handler for the second button as so:

CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

1269

protected void btnShowCookie_Click(object sender, EventArgs e)

{

string cookieData = "";

foreach (string s in Request.Cookies)

{

cookieData +=

string.Format("<li><b>Name</b>: {0}, <b>Value</b>: {1}</li>", s, Request.Cookies[s].Value);

}

lblCookieData.Text = cookieData;

}

If you now run the application and click your new button, you will find that the cookie data has indeed been sent by your browser (see Figure 33-9).

Figure 33-9. Viewing cookie data

Source Code The CookieStateApp project is included under the Chapter 33 subdirectory.

The Role of the <sessionState> Element

At this point in the chapter, you have examined numerous ways to remember information about your users. As you have seen, view state and application, cache, session, and cookie data are manipulated in more or less the same way (via a class indexer). As you have also seen, the HttpApplication type is often used to intercept and respond to events that occur during your web application’s lifetime.

By default, ASP.NET will store session state using an in-process *.dll hosted by the ASP.NET worker process (aspnet_wp.exe). Like any *.dll, the plus side is that access to the information is as

1270 CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

fast as possible. However, the downside is that if this AppDomain crashes (for whatever reason), all of the user’s state data is destroyed. Furthermore, when you store state data as an in-process *.dll, you cannot interact with a networked web farm. This default behavior is recorded in the

<sessionState> element of your machine.config file like so:

<sessionState

mode="InProc"

stateConnectionString="tcpip=127.0.0.1:42626" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" cookieless="false"

timeout="20"

/>

This default mode of storage works just fine if your web application is hosted by a single web server. As you might guess, however, this model is not ideal for a farm of web servers, given that session state is “trapped” within a given AppDomain.

Storing Session Data in the ASP.NET Session State Server

Under ASP.NET, you can instruct the runtime to host the session state *.dll in a surrogate process named the ASP.NET session state server (aspnet_state.exe). When you do so, you are able to offload the *.dll from aspnet_wp.exe into a unique *.exe, which can be located on any machine within the web farm. Even if you intend to run the aspnet_state.exe process on the same machine as the web server, you do gain the benefit of partitioning the state data in a unique process (as it is more durable).

To make use of the session state server, the first step is to start the aspnet_state.exe Windows service on the target machine. At the command line, simply type

net start aspnet_state

Alternatively, you can start aspnet_state.exe using the Services applet accessed from the Administrative Tools folder of the Control Panel, as shown in Figure 33-10.

The key benefit of this approach is that you can configure aspnet_state.exe to start automatically when the machine boots up using the Properties window. In any case, once the session state server is running, add the following <sessionState> element of your Web.config file as follows:

<sessionState

mode="StateServer"

stateConnectionString="tcpip=127.0.0.1:42626" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" cookieless="false"

timeout="20"

/>

Here, the mode attribute has been set to StateServer. That’s it! At this point, the CLR will host session-centric data within aspnet_state.exe. In this way, if the AppDomain hosting the web application crashes, the session data is preserved. Notice as well that the <sessionState> element can also support a stateConnectionString attribute. The default TCP/IP address value (127.0.0.1) points to the local machine. If you would rather have the .NET runtime use the aspnet_state.exe service located on another networked machine (again, think web farms), you are free to update this value.

CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

1271

Figure 33-10. Starting aspnet_state.exe using the Services applet

Storing Session Data in a Dedicated Database

Finally, if you require the highest degree of isolation and durability for your web application, you may choose to have the runtime store all your session state data within Microsoft SQL Server. The appropriate update to the Web.config file is simple:

<sessionState

mode="SQLServer"

stateConnectionString="tcpip=127.0.0.1:42626" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" cookieless="false"

timeout="20"

/>

However, before you attempt to run the associated web application, you need to ensure that the target machine (specified by the sqlConnectionString attribute) has been properly configured. When you install the .NET Framework 3.5 SDK (or Visual Studio 2008), you will be provided with two files named InstallSqlState.sql and UninstallSqlState.sql, located by default under C:\Windows\Microsoft.NET\Framework\<version>. On the target machine, you must run the InstallSqlState.sql file using a tool such as the Microsoft SQL Server Management Studio (which ships with Microsoft SQL Server 2005).

Once this SQL script has executed, you will find a new SQL Server database has been created (ASPState) that contains a number of stored procedures called by the ASP.NET runtime and a set of tables used to store the session data itself (also, the tempdb database has been updated with a set of tables for swapping purposes). As you would guess, configuring your web application to store session data within SQL Server is the slowest of all possible options. The benefit is that user data is as durable as possible (even if the web server is rebooted).