- Types of State
- Application State
- Session State
- Cookie State
- View State
- Summary
10.4 Cookie State
Although not part of the HTTP specification (yet), cookies are often used to store user preferences, session variables, or identity. The server issues a Set-Cookie header in its response to a client that contains the value it wants to store. The client is then expected to store the information associated with the URL or domain that issued the cookie. In subsequent requests to that URL or domain, the client should include the cookie information using the Cookie header. Some limitations of cookies include the fact that many browsers limit the amount of data sent through cookies (only 4,096 bytes are guaranteed) and that clients can potentially disable all cookie support in their browser.
ASP.NET provides an HttpCookie class for managing cookie data. Listing 10-16 shows the HttpCookie class definition and the cookie collection properties exposed by the request and response objects. Note that the request and response objects both expose the collection of cookies through the HttpCookieCollection type, which is just a type-safe derivative of the NameObjectCollectionBase class, designed for storing HttpCookie class instances. Each cookie can store multiple name/value pairs, as specified by RFC 2109, which are accessible through the Values collection of the HttpCookie class or indirectly through the default indexer provided by the class.
Listing 10-16: The HttpCookie Class
public sealed class HttpCookie { public string Domain {get; set;} public DateTime Expires {get; set;} public bool HasKeys {get; } public string this[string key] {get; set;} public string Name {get; set;} public string Path {get; set;} public string Secure {get; set;} public string Value {get; set;} public NameValueCollection Values {get; } //... } public sealed class HttpRequest { public HttpCookieCollection Cookies {get;} //... } public sealed class HttpResponse { public HttpCookieCollection Cookies {get;} //... }
To request that a client set a cookie, add a new HttpCookie instance to the response cookie collection before your page rendering. To access the cookies that the client is sending with any given request, access the Cookies collection property of the request object. Listing 10-17 shows an example of a page that sets and uses a cookie named "Age". If the cookie has not been set, the page adds the cookie to the Response.Cookies collection with a value from a field on the form (ageTextBox). If the cookie has been set, the current value is pulled from the Request.Cookies collection and is used instead.
Listing 10-17: Using Cookies in ASP.NET
protected void Page_Load(Object sender, EventArgs E) { int age = 0; if (Request.Cookies["Age"] == null) { // "Age" cookie not set, set with this response HttpCookie ac = new HttpCookie("Age"); ac.Value = ageTextBox.Text; Response.Cookies.Add(ac); age = Convert.ToInt32(ageTextBox.Text); } else { // use existing cookie value... age = Convert.ToInt32(Request.Cookies["Age"].Value); } // use age to customize page }
Although cookies are typically used to store user-specific configuration information and preferences, they can be used to store any client-specific state needed by an application (as long as that state is converted to string form). It is interesting to contrast our earlier shopping cart implementation using session state with an equivalent implementation using only cookies. The major change in our implementation is the population and retrieval of the shopping cart contents from cookies instead of directly from session state. This can be done by converting the contents of the shopping cart into string form so that it can be sent back as cookies to the client and later restored on subsequent requests. To facilitate this, we have added two new functions to our Item class: HydrateArrayListFromCookies and Save-ArrayListToCookies. The first function is called from within the Load event handler of our shopping Page class, and the second function is called from within the PreRender event handler. The implementation of these two functions is shown in Listing 10-18. The rest of our code remains the same because we have changed only how the ArrayList is persisted. Listing 10-19 shows the cookie-based implementation of our shopping cart application.
Listing 10-18: Item Class with Cookie Serialization Support
public class Item { public static ArrayList HydrateArrayListFromCookies() { int itemCount=0; HttpCookie itemCountCookie = HttpContext.Current.Request.Cookies["ItemCount"]; if (itemCountCookie != null) itemCount = Convert.ToInt32(itemCountCookie.Value); else { itemCountCookie = new HttpCookie("ItemCount"); itemCountCookie.Value = "0"; HttpContext.Current.Response.Cookies.Add( itemCountCookie); } ArrayList cart = new ArrayList(); for (int i=0; i<itemCount; i++) { HttpCookieCollection cookies = HttpContext.Current.Request.Cookies; int cost = Convert.ToInt32( cookies[i.ToString()+"cost"].Value); string desc = cookies[i.ToString()+"desc"].Value; cart.Add(new Item(desc, cost)); } return cart; } public static void SaveArrayListToCookies(ArrayList cart) { // Save array size first HttpCookie itemCountCookie = new HttpCookie("ItemCount"); itemCountCookie.Value = cart.Count.ToString(); HttpCookieCollection cookies = HttpContext.Current.Response.Cookies; cookies.Add(itemCountCookie); int i=0; foreach (Item item in cart) { HttpCookie descCookie = new HttpCookie(i.ToString() + "desc"); descCookie.Value = item.Description; cookies.Add(descCookie); HttpCookie costCookie = new HttpCookie(i.ToString() + "cost"); costCookie.Value = item.Cost.ToString(); cookies.Add(costCookie); i++; } } // remainder of class unchanged from Listing 10-4 }
Listing 10-19: Cookie State Shopping Page Example
public class PurchasePage : Page { // Maintain private cart array variable private ArrayList _cart; private void Page_Load(object sender, System.EventArgs e) { _cart = Item.HydrateArrayListFromCookies(); } private void Page_PreRender(object src, EventArgs e) { Item.SaveArrayListToCookies(_cart); } private void AddItem(string desc, int cost) { _cart.Add(new Item(desc, cost)); } // remaining code identical to Listing 10-7 }
Although it is technically possible to store any type of client-specific state using cookies, as shown in the previous shopping cart example, there are several drawbacks compared with other models. First, all of the state must be mapped into and out of strings, which in general requires more space to store the same amount of data. Second, as mentioned earlier, clients may disable cookies or may have a browser that does not support cookies, thus rendering the application inoperative. Finally, unlike session state, cookie state is passed between the client and the server with every request.