Chapter 8. State Management

The most significant difference between programming for the Web and programming for the desktop is state management—how you store information over the lifetime of your application. This information can be as simple as a user's name or as complex as a stuffed-full shopping cart for an e-commerce store.

In a traditional Windows application, there's little need to think about state management. Memory is plentiful and always available, and you only need to worry about a single user. In a web application, it's a different story. Thousands of users can simultaneously run the same application on the same computer (the web server), each one communicating over a stateless HTTP connection. These conditions make it impossible to design a web application like a traditional Windows program.

Understanding these state limitations is the key to creating efficient web applications. In this chapter, you'll see how you can use ASP.NET's state management features to store information carefully and consistently. You'll explore different storage options, including view state, session state, and custom cookies. You'll also consider how to transfer information from page to page using cross-page posting and the query string.

The Problem of State

In a traditional Windows program, users interact with a continuously running application. A portion of memory on the desktop computer is allocated to store the current set of working information.

In a web application, the story is quite a bit different. A professional ASP.NET site might look like a continuously running application, but that's really just a clever illusion. In a typical web request, the client connects to the web server and requests a page. When the page is delivered, the connection is severed, and the web server discards all the page objects from memory. By the time the user receives a page, the web page code has already stopped running, and there's no information left in the web server's memory.

This stateless design has one significant advantage. Because clients need to be connected for only a few seconds at most, a web server can handle a huge number of nearly simultaneous requests without a performance hit. However, if you want to retain information for a longer period of time so it can be used over multiple postbacks or on multiple pages, you need to take additional steps.

View State

One of the most common ways to store information is in view state. View state uses a hidden field that ASP.NET automatically inserts in the final, rendered HTML of a web page. It's a perfect place to store information that's used for multiple postbacks in a single web page.

In the previous chapters, you learned how web controls use view state to keep track of certain details. For example, if you change the text of a label, the Label control automatically stores its new text in view state. That way, the text remains in place the next time the page is posted back. Web controls store most of their property values in view state, provided you haven't switched ViewState off (for example, by setting the EnableViewState property of the control or the page to False).

View state isn't limited to web controls. Your web page code can add bits of information directly to the view state of the containing page and retrieve it later after the page is posted back. The type of information you can store includes simple data types and your own custom objects.

The ViewState Collection

The ViewState property of the page provides the current view state information. This property provides an instance of the StateBag collection class. The StateBag is a dictionary collection, which means every item is stored in a separate "slot" using a unique string name.

For example, consider this code:

' The Me keyword refers to the current Page object. It's optional.
Me.ViewState("Counter") = 1

This places the value 1 (or rather, an integer that contains the value 1) into the ViewState collection and gives it the descriptive name Counter. If currently no item has the name Counter, a new item will be added automatically. If an item is already stored under the name Counter, it will be replaced.

When retrieving a value, you use the key name. You also need to cast the retrieved value to the appropriate data type using the casting syntax you saw in Chapter 2 and Chapter 3. This extra step is required because the ViewState collection stores all items as basic objects, which allows it to handle many different data types.

Here's the code that retrieves the counter from view state and converts it to an integer:

Dim counter As Integer
counter = CType(Me.ViewState("Counter"), Integer)

Note

ASP.NET provides many collections that use the same dictionary syntax. This includes the collections you'll use for session and application state, as well as those used for caching and cookies. You'll see several of these collections in this chapter.

A View State Example

The following example is a simple counter program that records how many times a button is clicked. Without any kind of state management, the counter will be locked perpetually at 1. With careful use of view state, the counter works as expected.

Public Partial Class SimpleCounter
    Inherits System.Web.UI.Page

    Protected Sub cmdIncrement_Click(ByVal sender As Object, _
      ByVal e As EventArgs) Handles cmdIncrement.Click
Dim Counter As Integer
        If ViewState("Counter") Is Nothing Then
            Counter = 1
        Else
            Counter = CType(ViewState("Counter"), Integer) + 1
        End If

        ViewState("Counter") = Counter
        lblCount.Text = "Counter: " & Counter.ToString()
    End Sub

End Class

The code checks to make sure the item exists in view state before it attempts to retrieve it. Otherwise, you could easily run into problems such as the infamous null reference exception (which is described in Chapter 7).

Figure 8-1 shows the output for this page.

A simple view state counter

Figure 8.1. A simple view state counter

Making View State Secure

You probably remember from Chapter 5 that view state information is stored in a single jumbled string that looks like this:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"
 value="dDw3NDg2NTI5MDg7Oz4=" />

As you add more information to view state, this value can become much longer. Because this value isn't formatted as clear text, many ASP.NET programmers assume that their view state data is encrypted. It isn't. Instead, the view state information is simply patched together in memory and converted to a Base64 string (which is a special type of string that's always acceptable in an HTML document because it doesn't include any extended characters). A clever hacker could reverse-engineer this string and examine your view state data in a matter of seconds.

Tamper-Proof View State

If you want to make view state more secure, you have two choices. First, you can make sure the view state information is tamperproof by instructing ASP.NET to use a hash code. A hash code is sometimes described as a cryptographically strong checksum. The idea is that ASP.NET examines all the data in view state, just before it renders the final page. It runs this data through a hashing algorithm (with the help of a secret key value). The hashing algorithm creates a short segment of data, which is the hash code. This code is then added at the end of the view state data, in the final HTML that's sent to the browser.

When the page is posted back, ASP.NET examines the view state data and recalculates the hash code using the same process. It then checks whether the checksum it calculated matches the hash code that is stored in the view state for the page. If a malicious user changes part of the view state data, ASP.NET will end up with a new hash code that doesn't match. At this point, it will reject the postback completely. (You might think a really clever user could get around this by generating fake view state information and a matching hash code. However, malicious users can't generate the right hash code, because they don't have the same cryptographic key as ASP.NET. This means the hash codes they create won't match.)

Hash codes are actually enabled by default, so if you want this functionality, you don't need to take any extra steps.

Private View State

Even when you use hash codes, the view state data will still be readable by the user. In many cases, this is completely acceptable—after all, the view state tracks information that's often provided directly through other controls. However, if your view state contains some information you want to keep secret, you can enable view state encryption.

You can turn on encryption for an individual page using the ViewStateEncryptionMode property of the Page directive:

<%@Page ViewStateEncryptionMode="Always" %>

Or you can set the same attribute in a configuration file to configure view state encryption for all the pages in your website:

<configuration>
  <system.web>
    <pages viewStateEncryptionMode="Always" />
    ...
  </system.web>
</configuration>

Either way, this enforces encryption. You have three choices for your view state encryption setting—always encrypt (Always), never encrypt (Never), or encrypt only if a control specifically requests it (Auto). The default is Auto, which means that the page won't encrypt its view state unless a control on that page specifically requests it. (Technically, a control makes this request by calling the Page.RegisterRequiresViewStateEncryption() method.) If no control calls this method to indicate it has sensitive information, the view state is not encrypted, thereby saving the encryption overhead. On the other hand, a control doesn't have absolute power—if it calls Page.RegisterRequiresViewStateEncryption() and the encryption mode is Never, the view state won't be encrypted.

Tip

Don't encrypt view state data if you don't need to do so. The encryption will impose a performance penalty, because the web server needs to perform the encryption and decryption with each postback.

Retaining Member Variables

You have probably noticed that any information you set in a member variable for an ASP.NET page is automatically abandoned when the page processing is finished and the page is sent to the client. Interestingly, you can work around this limitation using view state.

The basic principle is to save all member variables to view state when the Page.PreRender event occurs and retrieve them when the Page.Load event occurs. Remember, the Load event happens every time the page is created. In the case of a postback, the Load event occurs first, followed by any other control events.

The following example uses this technique with a single member variable (named Contents). The page provides a text box and two buttons. The user can choose to save a string of text and then restore it at a later time (see Figure 8-2). The Button.Click event handlers store and retrieve this text using the Contents member variable. These event handlers don't need to save or restore this information using view state, because the PreRender and Load event handlers perform these tasks when page processing starts and finishes.

A page with state

Figure 8.2. A page with state

Public Partial Class PreserveMembers
    Inherits System.Web.UI.Page

    ' A member variable that will be cleared with every postback.
    Private Contents As String

    Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As EventArgs) Handles Me.Load
        If Me.IsPostBack Then
            ' Restore variables.
            Contents = CType(ViewState("Contents"), String)
        End If
    End Sub

    Protected Sub Page_PreRender(ByVal sender As Object, _
      ByVal e As EventArgs) Handles Me.PreRender
        ' Persist variables.
        ViewState("Contents") = Contents
    End Sub

    Protected Sub cmdSave_Click(ByVal sender As Object, _
      ByVal e As EventArgs) Handles cmdSave.Click
        ' Transfer contents of text box to member variable.
        Contents = txtValue.Text
        txtValue.Text = ""
    End Sub

    Protected Sub cmdLoad_Click(ByVal sender As Object, _
      ByVal e As EventArgs) Handles cmdLoad.Click
        ' Restore contents of member variable to text box.
        txtValue.Text = Contents
    End Sub

End Class

The logic in the Load and PreRender event handlers allows the rest of your code to work more or less as it would in a desktop application. However, you must be careful not to store needless amounts of information when using this technique. If you store unnecessary information in view state, it will enlarge the size of the final page output and can thus slow down page transmission times. Another disadvantage with this approach is that it hides the low-level reality that every piece of data must be explicitly saved and restored. When you hide this reality, it's more likely that you'll forget to respect it and design for it.

If you decide to use this approach to save member variables in view state, use it exclusively. In other words, refrain from saving some view state variables at the PreRender stage and others in control event handlers, because this is sure to confuse you and any other programmer who looks at your code.

Tip

The previous code example reacts to the Page.PreRender event, which occurs just after page processing is complete and just before the page is rendered in HTML. This is an ideal place to store any leftover information that is required. You cannot store view state information in an event handler for the Page.Unload event. Though your code will not cause an error, the information will not be stored in view state, because the final HTML page output is already rendered.

Storing Custom Objects

You can store your own objects in view state just as easily as you store numeric and string types. However, to store an item in view state, ASP.NET must be able to convert it into a stream of bytes so that it can be added to the hidden input field in the page. This process is called serialization. If your objects aren't serializable (and by default they're not), you'll receive an error message when you attempt to place them in view state.

To make your objects serializable, you need to add a Serializable attribute before your class declaration. For example, here's an exceedingly simple Customer class:

<Serializable()> _
Public Class Customer

    private _firstName As String
    Public Property FirstName() As String
        Get
            Return _firstName
        End Get
        Set(ByVal Value As String)
            _firstName = Value
        End Set
    End Property

    private _lastName As String
    Public Property LastName() As String
        Get
            Return _lastName
        End Get
        Set(ByVal Value As String)
            _lastName = Value
        End Set
    End Property

    Public Sub New(ByVal firstName As String, ByVal lastName As String)
        Me.FirstName = firstName
        Me.LastName = lastName
    End Sub
End Class

Because the Customer class is marked as serializable, it can be stored in view state:

' Store a customer in view state.
Dim cust As New Customer("Marsala", "Simons")
ViewState("CurrentCustomer") = cust

Remember, when using custom objects, you'll need to cast your data when you retrieve it from view state.

' Retrieve a customer from view state.
Dim cust As Customer
cust = CType(ViewState("CurrentCustomer"), Customer)

Once you understand this principle, you'll also be able to determine which .NET objects can be placed in view state. You simply need to find the class information in the Visual Studio Help. The easiest approach is to look the class up in the index. For example, to find out about the FileInfo class (which you'll learn about in Chapter 17), look for the index entry "FileInfo class." In the class documentation, you'll see the declaration for that class, which looks something like this:

<Serializable> _
<ComVisible(True)> _
Public NotInheritable Class FileInfo _
    Inherits FileSystemInfo

If the class declaration is preceded with the Serializable attribute (as it is here), instances of this class can be placed in view state. If the Serializable attribute isn't present, the class isn't serializable, and you won't be able to place instances of it in view state.

Transferring Information Between Pages

One of the most significant limitations with view state is that it's tightly bound to a specific page. If the user navigates to another page, this information is lost. This problem has several solutions, and the best approach depends on your requirements.

In the following sections, you'll learn two basic techniques to transfer information between pages: cross-page posting and the query string.

Cross-Page Posting

A cross-page postback is a technique that extends the postback mechanism you've already learned about so that one page can send the user to another page, complete with all the information for that page. This technique sounds conceptually straightforward, but it's a potential minefield. If you're not careful, it can lead you to create pages that are tightly coupled to others and difficult to enhance and debug.

The infrastructure that supports cross-page postbacks is a property named PostBackUrl, which is defined by the IButtonControl interface and turns up in button controls such as ImageButton, LinkButton, and Button. To use cross-posting, you simply set PostBackUrl to the name of another web form. When the user clicks the button, the page will be posted to that new URL with the values from all the input controls on the current page. (This posted-back information includes the hidden view state field. As you'll see shortly, it allows ASP.NET to create an up-to-date instance of the source page in memory.)

Here's an example—a page named CrossPage1.aspx that defines a form with two text boxes and a button. When the button is clicked, it posts to a page named CrossPage2.aspx.

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="CrossPage1.aspx.vb"
    Inherits="CrossPage1" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>CrossPage1</title>
</head>
<body>
    <form id="form1" runat="server" >
      <div>
        First Name:
        <asp:TextBox ID="txtFirstName" runat="server"></asp:TextBox>
        <br />
        Last Name:
        <asp:TextBox ID="txtLastName" runat="server"></asp:TextBox>
        <br />
        <br />
        <asp:Button runat="server" ID="cmdPost"
          PostBackUrl="CrossPage2.aspx" Text="Cross-Page Postback" /><br />
      </div>
    </form>
</body>
</html>

The CrossPage1 page doesn't include any code. Figure 8-3 shows how it appears in the browser.

The source of a cross-page postback

Figure 8.3. The source of a cross-page postback

Now if you load this page and click the button, the page will be posted back to CrossPage2.aspx. At this point, the CrossPage2.aspx page can interact with CrossPage1.aspx using the Page.PreviousPage property. Here's an event handler that grabs the title from the previous page and displays it:

Public Partial Class CrossPage2
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As EventArgs) Handles Me.Load

        If PreviousPage IsNot Nothing Then
            lblInfo.Text = "You came from a page titled " & _
              PreviousPage.Title
        End If

    End Sub

End Class

Note that this page checks for a null reference before attempting to access the PreviousPage object. If it's a null reference (Nothing), no cross-page postback took place. This means CrossPage2.aspx was requested directly or CrossPage2.aspx posted back to itself. Either way, no PreviousPage object is available.

Figure 8-4 shows what you'll see when CrossPage1.aspx posts to CrossPage2.aspx.

The target of a cross-page postback

Figure 8.4. The target of a cross-page postback

Getting More Information from the Source Page

The previous example shows an interesting initial test, but it doesn't really allow you to transfer any useful information. After all, you're probably interested in retrieving specific details (such as the text in the text boxes of CrossPage1.aspx) from CrossPage2.aspx. The title alone isn't very interesting.

To get more specific details, such as control values, you need to cast the PreviousPage reference to the appropriate page class (in this case it's the CrossPage1 class). Here's an example that handles this situation properly, by checking first whether the PreviousPage object is an instance of the expected class:

Protected Sub Page_Load(ByVal sender As Object, _
  ByVal e As EventArgs) Handles Me.Load

    Dim prevPage As CrossPage1
    prevPage = TryCast(PreviousPage, CrossPage1)

    If prevPage IsNot Nothing Then
        ' (Read some information from the previous page.)
    End If

End Sub

Rather than checking whether PreviousPage is the correct type of object and then casting it with CType(), this code uses a shortcut with TryCast(). If a conversion can't be made, TryCast() simply returns a null reference.

Note

In a projectless website, Visual Studio may flag this as an error, indicating that it does not have the type information for the source page class (in this example, that's CrossPage1). However, once you compile the website, the error will disappear.

You can also solve this problem in another way. Rather than casting the reference manually, you can add the PreviousPageType directive to the .aspx page that receives the cross-page postback (in this example, CrossPage2.aspx), right after the Page directive. The PreviousPageType directive indicates the expected type of the page initiating the cross-page postback. Here's an example:

<%@ PreviousPageType VirtualPath="~/CrossPage1.aspx" %>

Now, the PreviousPage property will automatically use the CrossPage1 type. That allows you to skip the casting code and go straight to work using the previous page object, like this:

Protected Sub Page_Load(ByVal sender As Object, _
  ByVal e As EventArgs) Handles Me.Load

    If PreviousPage IsNot Nothing Then
        ' (Read some information from the previous page.)
    End If

End Sub

However, this approach is more fragile because it limits you to a single page class. You don't have the flexibility to deal with situations where more than one page might trigger a cross-page postback. For that reason, it's usually more flexible to use the casting approach.

Once you've cast the previous page to the appropriate page type, you still won't be able to directly access the control objects it contains. That's because the controls on the web page are not publicly accessible to other classes. You can work around this by using properties.

For example, if you want to expose the values from two text boxes in the source page, you might add properties that wrap the control variables. Here are two properties you could add to the CrossPage1 class to expose its TextBox controls:

Public ReadOnly Property FirstNameTextBox() As TextBox
    Get
        Return txtFirstName
    End Get
End Property

Public ReadOnly Property LastNameTextBox() As TextBox
    Get
        Return txtLastName
    End Get
End Property

However, this usually isn't the best approach. The problem is that it exposes too many details, giving the target page the freedom to read everything from the text in the text box to its fonts and colors. If you need to change the page later to use different input controls, it will be difficult to maintain these properties. Instead, you'll probably be forced to rewrite code in both pages.

A better choice is to define specific, limited methods or properties that extract just the information you need. For example, you might decide to add a FullName property that retrieves just the text from the two text boxes. Here's the full page code for CrossPage1.aspx with this property:

Partial Class CrossPage1
    Inherits System.Web.UI.Page

    Public ReadOnly Property FullName() As String
        Get
            Return txtFirstName.Text & " " & txtLastName.Text
        End Get
    End Property

End Class

This way, the relationship between the two pages is clear, simple, and easy to maintain. You can probably change the controls in the source page (CrossPage1) without needing to change other parts of your application. For example, if you decide to use different controls for name entry in CrossPage1.aspx, you will be forced to revise the code for the FullName property. However, your changes would be confined to CrossPage1.aspx, and you wouldn't need to modify CrossPage2.aspx at all.

Here's how you can rewrite the code in CrossPage2.aspx to display the information from CrossPage1.aspx:

Protected Sub Page_Load(ByVal sender As Object, _
  ByVal e As EventArgs) Handles Me.Load

    If PreviousPage IsNot Nothing Then
        lblInfo.Text = "You came from a page titled " & _
          PreviousPage.Title & "<br />"

        Dim prevPage As CrossPage1
        prevPage = TryCast(PreviousPage, CrossPage1)
        If prevPage IsNot Nothing Then
lblInfo.Text &= "You typed in this: " & prevPage.FullName
        End If
    End If

End Sub

Notice that the target page (CrossPage2.aspx) can access the Title property of the previous page (CrossPage1.aspx) without performing any casting. That's because the Title property is defined as part of the base System.Web.UI.Page class, and so every web page includes it. However, to get access to the more specialized FullName property, you need to cast the previous page to the right page class (CrossPage1) or use the PreviousPageType directive that was discussed earlier.

Figure 8-5 shows the new result.

Retrieving specific information from the source page

Figure 8.5. Retrieving specific information from the source page

Note

Cross-page postbacks are genuinely useful, but they can lead the way to more complicated pages. If you allow multiple source pages to post to the same destination page, it's up to you to code the logic that figures out which page the user came from and then act accordingly. To avoid these headaches, it's easiest to perform cross-page postbacks between two specific pages only.

ASP.NET uses some interesting sleight of hand to make cross-page postbacks work. The first time the second page accesses Page.PreviousPage, ASP.NET needs to create the previous page object. To do this, it actually starts the page processing but interrupts it just before the PreRender stage, and it doesn't let the page render any HTML output.

However, this still has some interesting side effects. For example, all the page events of the previous page are fired, including Page.Load and Page.Init, and the Button.Click event also fires for the button that triggered the cross-page postback. ASP.NET fires these events because they might be needed to return the source page to the state it was last in, just before it triggered the cross-page postback.

The Query String

Another common approach is to pass information using a query string in the URL. This approach is commonly found in search engines. For example, if you perform a search on the Google website, you'll be redirected to a new URL that incorporates your search parameters. Here's an example:

http://www.google.ca/search?q=organic+gardening

The query string is the portion of the URL after the question mark. In this case, it defines a single variable named q, which contains the string organic+gardening.

The advantage of the query string is that it's lightweight and doesn't exert any kind of burden on the server. However, it also has several limitations:

  • Information is limited to simple strings, which must contain URL-legal characters.

  • Information is clearly visible to the user and to anyone else who cares to eavesdrop on the Internet.

  • The enterprising user might decide to modify the query string and supply new values, which your program won't expect and can't protect against.

  • Many browsers impose a limit on the length of a URL (usually from 1KB to 2KB). For that reason, you can't place a large amount of information in the query string and still be assured of compatibility with most browsers.

Adding information to the query string is still a useful technique. It's particularly well suited in database applications where you present the user with a list of items that correspond to records in a database, such as products. The user can then select an item and be forwarded to another page with detailed information about the selected item. One easy way to implement this design is to have the first page send the item ID to the second page. The second page then looks that item up in the database and displays the detailed information. You'll notice this technique in e-commerce sites such as Amazon.

To store information in the query string, you need to place it there yourself. Unfortunately, you have no collection-based way to do this. Instead, you'll need to insert it into the URL yourself. Here's an example that uses this approach with the Response.Redirect() method:

' Go to newpage.aspx. Submit a single query string argument
' named recordID, and set to 10.
Response.Redirect("newpage.aspx?recordID=10")

You can send multiple parameters as long as they're separated with an ampersand (&):

' Go to newpage.aspx. Submit two query string arguments:
' recordID (10) and mode (full).
Response.Redirect("newpage.aspx?recordID=10&mode=full")

The receiving page has an easier time working with the query string. It can receive the values from the QueryString dictionary collection exposed by the built-in Request object:

Dim ID As String = Request.QueryString("recordID")

Note that information is always retrieved as a string, which can then be converted to another simple data type. Values in the QueryString collection are indexed by the variable name. If you attempt to retrieve a value that isn't present in the query string, you'll get a null reference (Nothing).

Note

Unlike view state, information passed through the query string is clearly visible and unencrypted. Don't use the query string for information that needs to be hidden or made tamperproof.

A Query String Example

The next program presents a list of entries. When the user chooses an item by clicking the appropriate item in the list, the user is forwarded to a new page. This page displays the received ID number. This provides a quick and simple query string test with two pages. In a sophisticated application, you would want to combine some of the data control features that are described later in Part 3 of this book.

The first page provides a list of items, a check box, and a submission button (see Figure 8-6).

A query string sender

Figure 8.6. A query string sender

Here's the code for the first page:

Public Partial Class QueryStringSender
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As EventArgs) Handles Me.Load
        If Not Me.IsPostBack Then
            ' Add sample values.
lstItems.Items.Add("Econo Sofa")
            lstItems.Items.Add("Supreme Leather Drapery")
            lstItems.Items.Add("Threadbare Carpet")
            lstItems.Items.Add("Antique Lamp")
            lstItems.Items.Add("Retro-Finish Jacuzzi")
        End If
    End Sub

    Protected Sub cmdGo_Click(ByVal sender As Object, _
      ByVal e As EventArgs) Handles cmdGo.Click
        If lstItems.SelectedIndex = −1 Then
            lblError.Text = "You must select an item."
        Else
            ' Forward the user to the information page,
            ' with the query string data.
            Dim Url As String = "QueryStringRecipient.aspx?"
            Url &= "Item=" & lstItems.SelectedItem.Text & "&"
            Url &= "Mode=" & chkDetails.Checked.ToString()
            Response.Redirect(Url)
        End If
    End Sub

End Class

Here's the code for the recipient page (shown in Figure 8-7):

Public Partial Class QueryStringRecipient
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As EventArgs) Handles Me.Load
        lblInfo.Text = "Item: " & Request.QueryString("Item")
        lblInfo.Text &= "<br />Show Full Record: "
        lblInfo.Text &= Request.QueryString("Mode")
    End Sub

End Class
A query string recipient

Figure 8.7. A query string recipient

One interesting aspect of this example is that it places information in the query string that isn't valid—namely, the space that appears in the item name. When you run the application, you'll notice that ASP.NET encodes the string for you automatically, converting spaces to the valid %20 equivalent escape sequence. The recipient page reads the original values from the QueryString collection without any trouble. This automatic encoding isn't always sufficient. To deal with special characters, you should use the URL encoding technique described in the next section.

URL Encoding

One potential problem with the query string is that some characters aren't allowed in a URL. In fact, the list of characters that are allowed in a URL is much shorter than the list of allowed characters in an HTML document. All characters must be alphanumeric or one of a small set of special characters (including $-_.+!*'(),). Some browsers tolerate certain additional special characters (Internet Explorer is notoriously lax), but many do not. Furthermore, some characters have special meaning. For example, the ampersand (&) is used to separate multiple query string parameters, the plus sign (+) is an alternate way to represent a space, and the number sign (#) is used to point to a specific bookmark in a web page. If you try to send query string values that include any of these characters, you'll lose some of your data. You can test this with the previous example by adding items with special characters in the list box.

To avoid potential problems, it's a good idea to perform URL encoding on text values before you place them in the query string. With URL encoding, special characters are replaced by escaped character sequences starting with the percent sign (%), followed by a two-digit hexadecimal representation. For example, the & character becomes %26. The only exception is the space character, which can be represented as the character sequence %20 or the + sign.

To perform URL encoding, you use the UrlEncode() and UrlDecode() methods of the HttpServerUtility class. As you learned in Chapter 5, an HttpServerUtility object is made available to your code in every web form through the Page.Server property. The following code uses the UrlEncode() method to rewrite the previous example, so it works with product names that contain special characters:

Dim Url As String = "QueryStringRecipient.aspx?"
Url &= "Item=" & Server.UrlEncode(lstItems.SelectedItem.Text) & "&"
Url &= "Mode=" & chkDetails.Checked.ToString()
Response.Redirect(Url)

Notice that it's important not to encode everything. In this example, you can't encode the & character that joins the two query string values, because it truly is a special character.

You can use the UrlDecode() method to return a URL-encoded string to its initial value. However, you don't need to take this step with the query string. That's because ASP.NET automatically decodes your values when you access them through the Request.QueryString collection. (Many people still make the mistake of decoding the query string values a second time. Usually, decoding already decoded data won't cause a problem. The only exception is if you have a value that includes the + sign. In this case, using UrlDecode() will convert the + sign to a space, which isn't what you want.)

Cookies

Cookies provide another way that you can store information for later use. Cookies are small files that are created in the web browser's memory (if they're temporary) or on the client's hard drive (if they're permanent). One advantage of cookies is that they work transparently without the user being aware that information needs to be stored. They also can be easily used by any page in your application and even be retained between visits, which allows for truly long-term storage. They suffer from some of the same drawbacks that affect query strings—namely, they're limited to simple string information, and they're easily accessible and readable if the user finds and opens the corresponding file. These factors make them a poor choice for complex or private information or large amounts of data.

Some users disable cookies on their browsers, which will cause problems for web applications that require them. Also, users might manually delete the cookie files stored on their hard drives. But for the most part, cookies are widely adopted and used extensively on many websites.

Cookies are fairly easy to use. Both the Request and Response objects (which are provided through Page properties) provide a Cookies collection. The important trick to remember is that you retrieve cookies from the Request object, and you set cookies using the Response object.

To set a cookie, just create a new HttpCookie object. You can then fill it with string information (using the familiar dictionary pattern) and attach it to the current web response:

' Create the cookie object.
Dim cookie As New HttpCookie("Preferences")

' Set a value in it.
cookie("LanguagePref") = "English"

' Add another value.
cookie("Country") = "US"

' Add it to the current web response.
Response.Cookies.Add(cookie)

A cookie added in this way will persist until the user closes the browser and will be sent with every request. To create a longer-lived cookie, you can set an expiration date:

' This cookie lives for one year.
cookie.Expires = DateTime.Now.AddYears(1)

You retrieve cookies by cookie name using the Request.Cookies collection:

Dim cookie As HttpCookie = Request.Cookies("Preferences")

' Check to see whether a cookie was found with this name.
' This is a good precaution to take,
' because the user could disable cookies,
' in which case the cookie will not exist.
Dim language As String
If cookie IsNot Nothing Then
    language = cookie("LanguagePref")
End If

The only way to remove a cookie is by replacing it with a cookie that has an expiration date that has already passed. This code demonstrates the technique:

Dim cookie As New HttpCookie("Preferences")
cookie.Expires = DateTime.Now.AddDays(-1)
Response.Cookies.Add(cookie)

A Cookie Example

The next example shows a typical use of cookies to store a customer name (Figure 8-8). To try this example, begin by running the page, entering a name, and clicking the Create Cookie button. Then, close the browser, and request the page again. The second time, the page will find the cookie, read the name, and display a welcome message.

Displaying information from a custom cookie

Figure 8.8. Displaying information from a custom cookie

Here's the code for this page:

Public Partial Class CookieExample
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As EventArgs) Handles Me.Load
        Dim Cookie As HttpCookie = Request.Cookies("Preferences")
        If Cookie Is Nothing Then
            lblWelcome.Text = "<b>Unknown Customer</b>"
        Else
            lblWelcome.Text = "<b>Cookie Found.</b><br /><br />"
            lblWelcome.Text &= "Welcome, " & Cookie("Name")
        End If
    End Sub

    Protected Sub cmdStore_Click(ByVal sender As Object, _
      ByVal e As EventArgs) Handles cmdStore.Click
        Dim Cookie As HttpCookie = Request.Cookies("Preferences")
        If Cookie Is Nothing Then
            Cookie = New HttpCookie("Preferences")
        End If

        Cookie("Name") = txtName.Text
        Cookie.Expires = DateTime.Now.AddYears(1)
        Response.Cookies.Add(Cookie)

        lblWelcome.Text = "<b>Cookie Created.</b><br /><br />"
        lblWelcome.Text &= "New Customer: " & Cookie("Name")
    End Sub

End Class

Note

You'll find that some other ASP.NET features use cookies. Two examples are session state (which allows you to temporarily store user-specific information in server memory) and forms security (which allows you to restrict portions of a website and force users to access it through a login page). Chapter 19 discusses forms security, and the next section of this chapter discusses session state.

Session State

There comes a point in the life of most applications when they begin to have more sophisticated storage requirements. An application might need to store and access complex information such as custom data objects, which can't be easily persisted to a cookie or sent through a query string. Or the application might have stringent security requirements that prevent it from storing information about a client in view state or in a custom cookie. In these situations, you can use ASP.NET's built-in session state facility.

Session state management is one of ASP.NET's premiere features. It allows you to store any type of data in memory on the server. The information is protected, because it is never transmitted to the client, and it's uniquely bound to a specific session. Every client that accesses the application has a different session and a distinct collection of information. Session state is ideal for storing information such as the items in the current user's shopping basket when the user browses from one page to another.

Session Tracking

ASP.NET tracks each session using a unique 120-bit identifier. ASP.NET uses a proprietary algorithm to generate this value, thereby guaranteeing (statistically speaking) that the number is unique and it's random enough that a malicious user can't reverse-engineer or "guess" what session ID a given client will be using. This ID is the only piece of session-related information that is transmitted between the web server and the client.

When the client presents the session ID, ASP.NET looks up the corresponding session, retrieves the objects you stored previously, and places them into a special collection so they can be accessed in your code. This process takes place automatically.

For this system to work, the client must present the appropriate session ID with each request. You can accomplish this in two ways:

Using cookies:

In this case, the session ID is transmitted in a special cookie (named ASP.NET_SessionId), which ASP.NET creates automatically when the session collection is used. This is the default.

Using modified URLs:

In this case, the session ID is transmitted in a specially modified (or munged) URL. This allows you to create applications that use session state with clients that don't support cookies.

Session state doesn't come for free. Though it solves many of the problems associated with other forms of state management, it forces the server to store additional information in memory. This extra memory requirement, even if it is small, can quickly grow to performance-destroying levels as hundreds or thousands of clients access the site.

In other words, you must think through any use of session state. A careless use of session state is one of the most common reasons that a web application can't scale to serve a large number of clients. Sometimes a better approach is to use caching, as described in Chapter 23.

Using Session State

You can interact with session state using the System.Web.SessionState.HttpSessionState class, which is provided in an ASP.NET web page as the built-in Session object. The syntax for adding items to the collection and retrieving them is basically the same as for adding items to a page's view state.

For example, you might store a DataSet in session memory like this:

Session("InfoDataSet") = dsInfo

You can then retrieve it with an appropriate conversion operation:

dsInfo = CType(Session("InfoDataSet"), DataSet)

Of course, before you attempt to use the dsInfo object, you'll need to check that it actually exists—in other words, that it isn't a null reference (Nothing). If the dsInfo is null, it's up to you to regenerate it. (For example, you might decide to query a database to get the latest data.)

Note

Chapter 14 explores the DataSet.

Session state is global to your entire application for the current user. However, session state can be lost in several ways:

  • If the user closes and restarts the browser.

  • If the user accesses the same page through a different browser window, although the session will still exist if a web page is accessed through the original browser window. Browsers differ on how they handle this situation.

  • If the session times out due to inactivity. More information about session timeout can be found in the configuration section.

  • If your web page code ends the session by calling the Session.Abandon() method.

In the first two cases, the session actually remains in memory on the web server, because ASP.NET has no idea that the client has closed the browser or changed windows. The session will linger in memory, remaining inaccessible, until it eventually expires.

Table 8-1 describes the key methods and properties of the HttpSessionState class.

Table 8.1. HttpSessionState Members

Member

Description

Count

Provides the number of items in the current session collection.

IsCookieless

Identifies whether the session is tracked with a cookie or modified URLs.

IsNewSession

Identifies whether the session was created only for the current request. If no information is in session state, ASP.NET won't bother to track the session or create a session cookie. Instead, the session will be re-created with every request.

Keys

Gets a collection of all the session keys that are currently being used to store items in the session state collection.

Mode

Provides an enumerated value that explains how ASP.NET stores session state information. This storage mode is determined based on the web.config settings discussed in the "Session State Configuration" section later in this chapter.

SessionID

Provides a string with the unique session identifier for the current client.

Timeout

Determines the number of minutes that will elapse before the current session is abandoned, provided that no more requests are received from the client. This value can be changed programmatically, letting you make the session collection longer when needed.

Abandon()

Cancels the current session immediately and releases all the memory it occupied. This is a useful technique in a logoff page to ensure that server memory is reclaimed as quickly as possible.

Clear()

Removes all the session items but doesn't change the current session identifier.

A Session State Example

The next example uses session state to store several Furniture data objects. The data object combines a few related variables and uses a special constructor so it can be created and initialized in one easy line. Rather than use full property procedures, the class takes a shortcut and uses public member variables so that the code listing remains short and concise. (If you refer to the full code in the downloadable examples, you'll see that it uses property procedures.)

Public Class Furniture

    Public Name As String
    Public Description As String
    Public Cost As Double

    Public Sub New(ByVal name As String, _
      ByVal description As String, ByVal cost As Double)
        Me.Name = name
        Me.Description = description
        Me.Cost = cost
    End Sub

End Class

Three Furniture objects are created the first time the page is loaded, and they're stored in session state. The user can then choose from a list of furniture piece names. When a selection is made, the corresponding object will be retrieved, and its information will be displayed, as shown in Figure 8-9.

A session state example with data objects

Figure 8.9. A session state example with data objects

Public Partial Class SessionStateExample
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As EventArgs) Handles Me.Load

        If Me.IsPostBack = False Then
            ' Create Furniture objects.
            Dim Piece1 As New Furniture("Econo Sofa", _
                                        "Acme Inc.", 74.99)
            Dim Piece2 As New Furniture("Pioneer Table", _
                                        "Heritage Unit", 866.75)
            Dim Piece3 As New Furniture("Retro Cabinet", _
                                        "Sixties Ltd.", 300.11)

            ' Add objects to session state.
            Session("Furniture1") = Piece1
            Session("Furniture2") = Piece2
            Session("Furniture3") = Piece3

            ' Add rows to list control.
            lstItems.Items.Add(Piece1.Name)
            lstItems.Items.Add(Piece2.Name)
            lstItems.Items.Add(Piece3.Name)
        End If

        ' Display some basic information about the session.
        ' This is useful for testing configuration settings.
        lblSession.Text = "Session ID: " & Session.SessionID
        lblSession.Text &= "<br />Number of Objects: "
        lblSession.Text &= Session.Count.ToString()
        lblSession.Text &= "<br />Mode: " & Session.Mode.ToString()
        lblSession.Text &= "<br />Is Cookieless: "
        lblSession.Text &= Session.IsCookieless.ToString()
        lblSession.Text &= "<br />Is New: "
        lblSession.Text &= Session.IsNewSession.ToString()
        lblSession.Text &= "<br />Timeout (minutes): "
        lblSession.Text &= Session.Timeout.ToString()
    End Sub

    Protected Sub cmdMoreInfo_Click(ByVal sender As Object, _
      ByVal e As EventArgs) Handles cmdMoreInfo.Click

        If lstItems.SelectedIndex = −1 Then
            lblRecord.Text = "No item selected."
        Else
            ' Construct a key name based on the index.
            ' For example, Furniture1, Furniture2, and so on.
            Dim Key As String
            Key = "Furniture" & _
                  (lstItems.SelectedIndex + 1).ToString()

            ' Retrieve the Furniture object from session state.
Dim Piece As Furniture = CType(Session(Key), Furniture)

            ' Display the information for this object.
            lblRecord.Text = "Name: " & Piece.Name
            lblRecord.Text &= "<br />Manufacturer: "
            lblRecord.Text &= Piece.Description
            lblRecord.Text &= "<br />Cost: " & Piece.Cost.ToString("c")
        End If
    End Sub

End Class

It's also a good practice to add a few session-friendly features in your application. For example, you could add a logout button to the page that automatically cancels a session using the Session.Abandon() method. This way, the user will be encouraged to terminate the session rather than just close the browser window, and the server memory will be reclaimed faster.

Session State Configuration

You configure session state through the web.config file for your current application (which is found in the same virtual directory as the .aspx web page files). The configuration file allows you to set advanced options such as the timeout and the session state mode.

The following listing shows the most important options that you can set for the <sessionState> element. Keep in mind that you won't use all of these details at the same time. Some settings apply only to certain session state modes, as you'll see shortly.

<?xml version="1.0" ?>
<configuration>
    <system.web>
        <!-- Other settings omitted. -->

        <sessionState
            cookieless="UseCookies"
            cookieName="ASP.NET_SessionId"
            regenerateExpiredSessionId="false"
            timeout="20"
            mode="InProc"
            stateConnectionString="tcpip=127.0.0.1:42424"
            stateNetworkTimeout="10"
            sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI"
            sqlCommandTimeout="30"
            allowCustomSqlDatabase="false"
            customProvider=""
            compressionEnabled="false"
        />
    </system.web>
</configuration>

The following sections describe the most important session state settings.

Cookieless

You can set the cookieless setting to one of the values defined by the HttpCookieMode enumeration, as described in Table 8-2.

Table 8.2. HttpCookieMode Values

Value

Description

UseCookies

Cookies are always used, even if the browser or device doesn't support cookies or they are disabled. This is the default. If the device does not support cookies, session information will be lost over subsequent requests, because each request will get a new ID.

UseUri

Cookies are never used, regardless of the capabilities of the browser or device. Instead, the session ID is stored in the URL.

UseDeviceProfile

ASP.NET chooses whether to use cookieless sessions by examining the BrowserCapabilities object. The drawback is that this object indicates what the device should support—it doesn't take into account that the user may have disabled cookies in a browser that supports them.

AutoDetect

ASP.NET attempts to determine whether the browser supports cookies by attempting to set and retrieve a cookie (a technique commonly used on the Web). This technique can correctly determine whether a browser supports cookies but has them disabled, in which case cookieless mode is used instead.

Here's an example that forces cookieless mode (which is useful for testing):

<sessionState cookieless="UseUri" ... />

In cookieless mode, the session ID will automatically be inserted into the URL. When ASP.NET receives a request, it will remove the ID, retrieve the session collection, and forward the request to the appropriate directory. Figure 8-10 shows a munged URL.

A munged URL with the session ID

Figure 8.10. A munged URL with the session ID

Because the session ID is inserted in the current URL, relative links also automatically gain the session ID. In other words, if the user is currently stationed on Page1.aspx and clicks a relative link to Page2.aspx, the relative link includes the current session ID as part of the URL. The same is true if you call Response.Redirect() with a relative URL, as shown here:

Response.Redirect("Page2.aspx")

Figure 8-11 shows a sample website (included with the online samples in the CookielessSessions directory) that tests cookieless sessions. It contains two pages and uses cookieless mode. The first page (Cookieless1.aspx) contains a HyperLink control and two buttons, all of which take you to a second page (Cookieless2.aspx). The trick is that these controls have different ways of performing their navigation. Only two of them work with cookieless session—the third loses the current session.

Three tests of cookieless sessions

Figure 8.11. Three tests of cookieless sessions

The HyperLink control navigates to the page specified in its NavigateUrl property, which is set to the relative path Cookieless2.aspx. If you click this link, the session ID is retained in the URL, and the new page can retrieve the session information. This demonstrates that cookieless sessions work with relative links.

The two buttons on this page use programmatic redirection by calling the Response.Redirect() method. The first button uses the relative path Cookieless2.aspx, much like the HyperLink control. This approach works with cookieless session state and preserves the munged URL with no extra steps required.

Protected Sub cmdLink_Click(ByVal sender As Object, _
  ByVal As EventArgs) Handles cmdLink.Click

    Response.Redirect("Cookieless2.aspx")
End Sub

The only real limitation of cookieless state is that you cannot use absolute links (links that include the full URL, starting with http://). The second button uses an absolute link to demonstrate this problem. Because ASP.NET cannot insert the session ID into the URL, the session is lost.

Protected Sub cmdLinkAbsolute_Click(ByVal sender As Object, _
  ByVal e As EventArgs) Handles cmdLinkAbsolute.Click

    Response.Redirect("http://localhost:56371/CookielessSessions/Cookieless2.aspx")
End Sub

Now the target page (Figure 8-12) checks for the session information but can't find it.

A lost session

Figure 8.12. A lost session

Writing the code to demonstrate this problem in a test environment is a bit tricky. The problem is that Visual Studio's integrated web server chooses a different port for your website every time you start it. As a result, you'll need to edit the code every time you open Visual Studio so that your URL uses the right port number (such as 56371 in the previous example).

There's another workaround. You can use some crafty code that gets the current URL from the page and just modifies the last part of it (changing the page name from Cookieless1.aspx to Cookieless2.aspx). Here's how:

' Create a new URL based on the current URL (but ending with
' the page Cookieless2.aspx instead of Cookieless1.aspx.
Dim url As String = "http://" & Request.Url.Authority & _
  Request.Url.Segments(0) & Request.Url.Segments(1) & _
  "Cookieless2.aspx"

Response.Redirect(url)

Of course, if you deploy your website to a real virtual directory that's hosted by IIS, you won't use a randomly chosen port number anymore, and you won't experience this quirk. Chapter 26 has more about virtual directories and website deployment.

Timeout

Another important session state setting in the web.config file is the timeout. This specifies the number of minutes that ASP.NET will wait, without receiving a request, before it abandons the session.

<sessionState timeout="20" ... />

This setting represents one of the most important compromises of session state. A difference of minutes can have a dramatic effect on the load of your server and the performance of your application. Ideally, you will choose a timeframe that is short enough to allow the server to reclaim valuable memory after a client stops using the application but long enough to allow a client to pause and continue a session without losing it.

You can also programmatically change the session timeout in code. For example, if you know a session contains an unusually large amount of information, you may need to limit the amount of time the session can be stored. You would then warn the user and change the Timeout property. Here's a sample line of code that changes the timeout to 10 minutes:

Session.Timeout = 10

Mode

The remaining session state settings allow you to configure ASP.NET to use different session state services, depending on the mode that you choose. The next few sections describe the modes you can choose from.

Note

Changing the mode is an advanced configuration task. To do it successfully, you need to understand the environment in which your web application will be deployed. For example, if you're deploying your application to a third-party web host, you need to know whether the host supports other modes before you try to use them. If you're deploying your application to a network server in your own organization, you need to team up with your friendly neighborhood network administrator.

InProc

InProc is the default mode, and it makes the most sense for small websites. It instructs information to be stored in the same process as the ASP.NET worker threads, which provides the best performance but the least durability. If you restart your server, the state information will be lost. (In ASP.NET, application domains can be restarted for a variety of reasons, including configuration changes and updated pages, and when certain thresholds are met. If you find that you're losing sessions before the timeout limit, you may want to experiment with a more durable mode.)

InProc mode won't work if you're using a web farm, which is a load-balancing arrangement that uses multiple web servers to run your website. In this situation, different web servers might handle consecutive requests from the same user. If the web servers use InProc mode, each one will have its own private collection of session data. The end result is that users will unexpectedly lose their sessions when they travel to a new page or post back the current one.

Note

When using the StateServer and SQLServer modes, the objects you store in session state must be serializable. Otherwise, ASP.NET will not be able to transmit the object to the state service or store it in the database. Earlier in this chapter, you learned how to create a serializable Customer class for storing in view state.

Off

This setting disables session state management for every page in the application. This can provide a slight performance improvement for websites that are not using session state.

StateServer

With this setting, ASP.NET will use a separate Windows service for state management. This service runs on the same web server, but it's outside the main ASP.NET process, which gives it a basic level of protection if the ASP.NET process needs to be restarted. The cost is the increased time delay imposed when state information is transferred between two processes. If you frequently access and change state information, this can make for a fairly unwelcome slowdown.

When using the StateServer setting, you need to specify a value for the stateConnectionString setting. This string identifies the TCP/IP address of the computer that is running the StateServer service and its port number (which is defined by ASP.NET and doesn't usually need to be changed). This allows you to host the StateServer on another computer. If you don't change this setting, the local server will be used (set as address 127.0.0.1).

Of course, before your application can use the service, you need to start it. The easiest way to do this is to use the Microsoft Management Console (MMC). Here's how:

  1. Select Start

    StateServer
  2. Open the Administrative Tools group, and then choose Computer Management.

  3. In the Computer Management tool, go to the Services and Applications

    StateServer
  4. Find the service called ASP.NET State Service in the list, as shown in Figure 8-13.

    The ASP.NET state service

    Figure 8.13. The ASP.NET state service

  5. Once you find the service in the list, you can manually start and stop it by right-clicking it. Generally, you'll want to configure Windows to automatically start the service. Right-click it, select Properties, and modify the Startup Type, setting it to Automatic, as shown in Figure 8-14.

Changing the startup type

Figure 8.14. Changing the startup type

Note

When using StateServer mode, you can also set an optional stateNetworkTimeout attribute that specifies the maximum number of seconds to wait for the service to respond before canceling the request. The default value is 10 (seconds).

SQLServer

This setting instructs ASP.NET to use an SQL Server database to store session information, as identified by the sqlConnectionString attribute. This is the most resilient state store but also the slowest by far. To use this method of state management, you'll need to have a server with SQL Server installed.

When setting the sqlConnectionString attribute, you follow the same sort of pattern you use with ADO.NET data access. Generally, you'll need to specify a data source (the server address) and a user ID and password, unless you're using SQL integrated security.

In addition, you need to install the special stored procedures and temporary session databases. These stored procedures take care of storing and retrieving the session information. ASP.NET includes a command-line tool that does the work for you automatically, called aspnet_regsql.exe. It's found in the c:WindowsMicrosoft.NETFramework[Version] directory (where [Version] is the current version of .NET, such as v4.0.30319). The easiest way to run aspnet_regsql.exe is to start by launching the Visual Studio command prompt (open the Start menu and choose Programs

SQLServer

You can use the aspnet_regsql.exe tool to perform several database-related tasks. As you travel through this book, you'll see how to use aspnet_regsql.exe with ASP.NET features like membership (Chapter 20), profiles (Chapter 21), and caching (Chapter 23). To use aspnet_regsql.exe to create a session storage database, you supply the –ssadd parameter. In addition, you use the –S parameter to indicate the database server name, and the –E parameter to log in to the database using the currently logged-in Windows user account.

Here's a command that creates the session storage database on the current computer, using the default database name ASPState:

aspnet_regsql.exe -S localhost -E –ssadd

This command uses the alias localhost, which tells aspnet_regsql.exe to connect to the database server on the current computer.

Note

The aspnet_regsql.exe command supports additional options that allow you to store session information in a database with a different name. You can find out about these options by referring to the Visual Studio help (look up aspnet_regsql in the index) or by surfing to http://msdn2.microsoft.com/library/ms178586.aspx. This information also describes the extra steps you need to take to use the database-backed session storage with SQL Server Express.

Once you've created your session state database, you need to tell ASP.NET to use it by modifying the <sessionState> section of the web.config file. If you're using a database named ASPState to store your session information (which is the default), you don't need to supply the database name. Instead, you simply have to indicate the location of the server and the type of authentication that ASP.NET should use to connect to it, as shown here:

<sessionState mode="SQLServer"
 sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI"
 ... />

When using the SQLServer mode, you can also set an optional sqlCommandTimeout attribute that specifies the maximum number of seconds to wait for the database to respond before canceling the request. The default is 30 seconds.

Custom

When using custom mode, you need to indicate which session state store provider to use by supplying the customProvider attribute. The customProvider attribute indicates the name of the class. The class may be part of your web application (in which case the source code is placed in the App_Code subfolder), or it can be in an assembly that your web application is using (in which case the compiled assembly is placed in the Bin subfolder).

Creating a custom state provider is a low-level task that needs to be handled carefully to ensure security, stability, and scalability. Custom state providers are also beyond the scope of this book. However, other vendors may release custom state providers you want to use. For example, Oracle could provide a custom state provider that allows you to store state information in an Oracle database.

Compression

When you set enableCompression to true, session data is compressed before it's passed out of process. The enableCompression setting only has an effect when you're using out-of-process session state storage, because it's only in this situation that the data is serialized.

To compress and decompress session data, the web server needs to perform additional work. However, this isn't usually a problem, because compression is used in scenarios where web servers have plenty of CPU time to spare but are limited by other factors. There are two key scenarios where session-state compression makes sense:

When storing huge amounts of session state data in memory:

Web server memory is a precious resource. Ideally, session state is used for relatively small chunks of information, while a database deals with the long-term storage of larger amounts of data. But if this isn't the case and if the out-of-process state server is hogging huge amounts of memory, compression is a potential solution.

When storing session state data on another computer:

In some large-scale web applications, session state is stored out of process (usually in SQL Server) and on a separate computer. As a result, ASP.NET needs to pass the session information back and forth over a network connection. Clearly, this design reduces performance from the speeds you'll see when session state is stored on the web server computer. However, it's still the best compromise for some heavily trafficked web applications with huge session state storage needs.

The actual amount of compression varies greatly depending on the type of data, but in testing Microsoft saw clients achieve 30 percent to 60 percent size reductions, which is enough to improve performance in these specialized scenarios.

Application State

Application state allows you to store global objects that can be accessed by any client. Application state is based on the System.Web.HttpApplicationState class, which is provided in all web pages through the built-in Application object.

Application state is similar to session state. It supports the same type of objects, retains information on the server, and uses the same dictionary-based syntax. A common example with application state is a global counter that tracks how many times an operation has been performed by all the web application's clients.

For example, you could create a global.asax event handler that tracks how many sessions have been created or how many requests have been received into the application. Or you can use similar logic in the Page.Load event handler to track how many times a given page has been requested by various clients. Here's an example of the latter:

Protected Sub Page_Load(ByVal sender As Object, _
  ByVal e As EventArgs) Handles Me.Load

    ' Retrieve the current counter value.
    Dim Count As Integer = CType(Application("HitCounterForOrderPage"), Integer)

    ' Increment the counter.
    Count += 1
' Store the current counter value.
    Application("HitCounterForOrderPage") = Count
    lblCounter.Text = Count.ToString()

End Sub

Once again, application state items are stored as objects, so you need to cast them when you retrieve them from the collection. Items in application state never time out. They last until the application or server is restarted or the application domain refreshes itself (because of automatic process recycling settings or an update to one of the pages or components in the application).

Application state isn't often used, because it's generally inefficient. In the previous example, the counter would probably not keep an accurate count, particularly in times of heavy traffic. For example, if two clients requested the page at the same time, you could have a sequence of events like this:

  1. User A retrieves the current count (432).

  2. User B retrieves the current count (432).

  3. User A sets the current count to 433.

  4. User B sets the current count to 433.

In other words, one request isn't counted because two clients access the counter at the same time. To prevent this problem, you need to use the Lock() and Unlock() methods, which explicitly allow only one client to access the Application state collection at a time.

Protected Sub Page_Load(ByVal sender As Object, _
  ByVal e As EventArgs) Handles Me.Load

    ' Acquire exclusive access.
    Application.Lock()

    Dim Count As Integer = CType(Application("HitCounterForOrderPage"), Integer)
    Count += 1
    Application("HitCounter") = Count

    ' Release exclusive access.
    Application.Unlock()

    lblCounter.Text = Count.ToString()

End Sub

Unfortunately, all other clients requesting the page will be stalled until the Application collection is released. This can drastically reduce performance. Generally, frequently modified values are poor candidates for application state. In fact, application state is rarely used in the .NET world because its two most common uses have been replaced by easier, more efficient methods:

  • In the past, application state was used to store application-wide constants, such as a database connection string. As you saw in Chapter 5, this type of constant can be stored in the web.config file, which is generally more flexible because you can change it easily without needing to hunt through web page code or recompile your application.

  • Application state can also be used to store frequently used information that is time-consuming to create, such as a full product catalog that requires a database lookup. However, using application state to store this kind of information raises all sorts of problems about how to check whether the data is valid and how to replace it when needed. It can also hamper performance if the product catalog is too large. Chapter 23 introduces a similar but much more sensible approach—storing frequently used information in the ASP.NET cache. Many uses of application state can be replaced more efficiently with caching.

Tip

If you decide to use application state, you can initialize its contents when your application first starts. Just add the initialization code to the global.asax file in a method named Application_OnStart(), as described in Chapter 5.

An Overview of State Management Choices

Each state management choice has a different lifetime, scope, performance overhead, and level of support. Table 8-3 and Table 8-4 show an at-a-glance comparison of your state management options.

Table 8.3. State Management Options Compared (Part 1)

 

View State

Query String

Custom Cookies

Allowed Data Types

All serializable .NET data types.

A limited amount of string data.

String data.

Storage Location

A hidden field in the current web page.

The browser's URL string.

The client's computer (in memory or a small text file, depending on its lifetime settings).

Lifetime

Retained permanently for postbacks to a single page.

Lost when the user enters a new URL or closes the browser. However, this can be stored in a bookmark.

Set by the programmer. Can be used in multiple pages and can persist between visits.

Scope

Limited to the current page.

Limited to the target page.

The whole ASP.NET application.

Security

Tamperproof by default but easy to read. You can enforce encryption by using the ViewStateEncryptionMode property of the Page directive.

Clearly visible and easy for the user to modify.

Insecure, and can be modified by the user.

Performance Implications

Slow if a large amount of information is stored, but will not affect server performance.

None, because the amount of data is trivial.

None, because the amount of data is trivial.

Typical Use

Page-specific settings.

Sending a product ID from a catalog page to a details page.

Personalization preferences for a website.

Table 8.4. State Management Options Compared (Part 2)

 

Session State

Application State

Allowed Data Types

All .NET data types for the default in-process storage mode. All serializable .NET data types if you use an out-of-process storage mode.

All .NET data types.

Storage Location

Server memory, state service, or SQL Server, depending on the mode you choose.

Server memory.

Lifetime

Times out after a predefined period (usually 20 minutes, but can be altered globally or programmatically).

The lifetime of the application (typically, until the server is rebooted).

Scope

The whole ASP.NET application.

The whole ASP.NET application. Unlike other methods, application data is global to all users.

Security

Very secure, because data is never transmitted to the client.

Very secure, because data is never transmitted to the client.

Performance Implications

Slow when storing a large amount of information, especially if there are many users at once, because each user will have their own copy of session data.

Slow when storing a large amount of information, because this data will never time out and be removed.

Typical Use

Storing items in a shopping basket.

Storing any type of global data.

Note

ASP.NET has another, more specialized type of state management called profiles. Profiles allow you to store and retrieve user-specific information from a database. The only catch is that you need to authenticate the user in order to get the right information. You'll learn about profiles in Chapter 21.

The Last Word

State management is the art of retaining information between requests. Usually, this information is user-specific (such as a list of items in a shopping cart, a user name, or an access level), but sometimes it's global to the whole application (such as usage statistics that track site activity). Because ASP.NET uses a disconnected architecture, you need to explicitly store and retrieve state information with each request. The approach you choose to store this data can dramatically affect the performance, scalability, and security of your application. Remember to consult Table 8-3 and Table 8-4 to help evaluate different types of state management and determine what is best for your needs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.93.64