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
NotInheritable Public Class HttpCookie Public Property Domain As String Public Property Expires As DateTime Public ReadOnly Property HasKeys As Boolean Public Default Property Item( _ ByVal key As String) As String Public Property Name As String Public Property Path As String Public Property Secure As String Public Property Value As String Public ReadOnly Property Values As NameValueCollection '... End Class NotInheritable Public Class HttpRequest Public ReadOnly Property Cookies As HttpCookieCollection '... End Class NotInheritable Public Class HttpResponse Public ReadOnly Property Cookies As HttpCookieCollection '... End Class
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
Private Sub Page_Load(ByVal sender As Object, _ ByVal e As EventArgs) Dim age As Integer = 0 If (Request.Cookies("Age") Is Nothing) Then ' "Age" cookie not set, set with this response Dim ac As HttpCookie = 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) End If ' use age to customize page End Sub
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 Shared Function HydrateArrayListFromCookies() _ As ArrayList Dim itemCount As Integer = 0 Dim itemCountCookie As HttpCookie = _ HttpContext.Current.Request.Cookies("ItemCount") If (Not itemCountCookie Is Nothing) Then itemCount = Convert.ToInt32(itemCountCookie.Value) Else itemCountCookie = New HttpCookie("ItemCount") itemCountCookie.Value = "0" HttpContext.Current.Response.Cookies._ Add(itemCountCookie) End If Dim cart As ArrayList = New ArrayList() Dim i As Integer Dim cn As String For i = 0 To itemCount - 1 cn = i.ToString() & "cost" Dim cost As Integer = Convert.ToInt32( _ HttpContext.Current.Request.Cookies(cn).Value) cn = i.ToString() & "desc" Dim desc As String = _ HttpContext.Current.Request.Cookies(cn).Value cart.Add(New Item(desc, cost)) Next i Return cart End Function Public Shared Sub SaveArrayListToCookies(_ ByVal cart As ArrayList) ' Save array size first Dim itemCountCookie As HttpCookie = _ New HttpCookie("ItemCount") itemCountCookie.Value = cart.Count.ToString() HttpContext.Current.Response.Cookies.Add( _ itemCountCookie) Dim i As Integer = 0 Dim it As Item For Each it In cart Dim descCookie As HttpCookie = _ New HttpCookie(i.ToString() + "desc") descCookie.Value = it.Description HttpContext.Current.Response.Cookies.Add(descCookie) Dim costCookie As HttpCookie = _ New HttpCookie(i.ToString() & "cost") costCookie.Value = it.Cost.ToString() HttpContext.Current.Response.Cookies.Add(costCookie) i = i + 1 Next it End Sub ' remainder of class unchanged from Listing 10-4 End Class
Listing 10-19: Cookie State Shopping Page Example
Public Class PurchasePage Inherits Page ' Maintain private cart array variable Private m_Cart As ArrayList Private Sub Page_Load(ByVal sender As Object, _ ByVal e As EventArgs) m_Cart = Item.HydrateArrayListFromCookies() End Sub Private Sub Page_PreRender(ByVal sender As Object, _ ByVal e As EventArgs) _ Handles MyBase.PreRender Item.SaveArrayListToCookies(m_Cart) End Sub Private Sub AddItem(ByVal desc As String, _ ByVal cost As Integer) m_Cart.Add(New Item(desc, cost)) End Sub ' remaining code identical to Listing 10-7 End Class
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.