JavaScript Editor Javascript validator     Javascripts

Main Page


Previous Section Next Section

6.2 State

State is the current value of all the controls and variables for the current user in the current session. The web is inherently a stateless environment, which means that every time a page is posted to the server and then sent back to the browser, the page is recreated from scratch. Unless you explicitly preserve the state of all the controls before the page is posted, the state is lost and all the controls are created with default values. One of the great strengths of ASP.NET is that it automatically maintains state for server controls—both HTML and ASP. This section will explore how that is done and how you can use the ASP.NET state management capabilities.

ASP.NET manages three types of state:

  • View state (which is saved in the state bag)

  • Application state

  • Session state

Table 6-3 compares the different kinds of state.

Table 6-3. Comparison of types of state
 

View state

Application state

Session state

Uses server resources

No

Yes

Yes

Uses bandwidth

Yes

No

Depends

Times out

No

No

Yes

Security exposure

Yes

No

Depends

Optimized for non-primitive types

No

Yes

Yes

Available for arbitrary data

Yes

Yes

Yes

Programmatically accessible

Yes

Yes

Yes

Scope

Page

Application

Session

Survives restart

Yes

No

Depends

The following sections will examine each type of state in turn.

6.2.1 View State

The view state is the state of the page and all its controls. The view state is automatically maintained across posts by the ASP.NET Framework. When a page is posted to the server, the view state is read. Just before the page is sent back to the browser the view state is restored.

The view state is saved in the state bag (described in the next section) via hidden fields on the page that contain the state encoded in a string variable. Since the view state is maintained via form fields, it works with all browsers.

If there is no need to maintain the view state for a page, you can boost performance by disabling view state for that page. For example, if the page does not post back to itself or if the only control on a page that might need to have its state maintained is populated from a database with every round trip to the server, then there is no need to maintain the view state for that page. To disable view state for a page, add the EnableViewState attribute with a value of false to the Page directive:

<%@ Page Language="C#"  EnableViewState="false" %>

The default value for EnableViewState is true. Alternatively, omit the server-side form tag (<form runat="server">), although this will also disable all server-side processing and controls.

The view state can be disabled for an entire application by setting the EnableViewState property to false in the <pages> section of the machine.config or web.config configuration file (described in Chapter 20).

It is also possible to maintain or disable view state for specific controls. This is done with the Control.EnableViewState property, which is a Boolean value with a default of true. Disabling view state for a control, just as for the page, will improve performance. This would be appropriate, for example, in a situation where a DataGrid is populated from a database every time the page is loaded. In this case, the contents of the control would simply be overridden by the database query, so there is no point in maintaining view state for that control. If the DataGrid in question were named dg, the following line of code (identical in C# and VB.NET except for the trailing semicolon in C#) would disable its view state:

dg.EnableViewState = false

There are some situations where view state is not the best place to store data. If there is a large amount of data to be stored, then view state is not an efficient mechanism, since the data is transferred back and forth to the server with every page post. If there are security concerns about the data and it is not otherwise being displayed on the page, then including the data in view state increases the security exposure. Finally, view state is optimized only for strings, integers, Booleans, arrays, ArrayLists, and hashtables. Other .NET types may be serialized and persisted in view state, but will result in degraded performance and a larger view state footprint.

In some of these instances, session state might be a better alternative; on the other hand, view state does not consume any server resources and does not time out, as does session state.

6.2.2 State Bag

If there are values that are not associated with any control and you wish to preserve these values across round trips, you can store these values in the page's state bag. The state bag is a data structure containing attribute/value pairs, stored as strings associated with objects. The valid objects are the primitive data types—integers, bytes, strings, Booleans, and so on. The state bag is implemented using the StateBag class, which is a dictionary object. You add or remove items from the state bag as with any dictionary object. For a complete discussion of dictionary objects in C#, see Programming C#, Third Edition, by Jesse Liberty (O'Reilly), and in VB.NET see VB.NET Language in a Nutshell by Steven Roman, Ron Petrusha, and Paul Lomax, (O'Reilly).

The state bag is maintained using the same hidden fields as the view state. You can set and retrieve values of things in the state bag using the ViewState keyword, as shown in Example 6-5 in C# and in Example 6-6 in VB.NET. These examples set up a counter that is maintained as long as the session is active. Every time the Increment Counter button is clicked, the page is reloaded, which causes the counter to increment.

Example 6-5. Using the StateBag using C#, csStateBagDemo.aspx
<%@ Page Language="C#" %>
<html>

<script runat="server">

   void Page_Load(Object sender, EventArgs e) 
   {
      lblCounter.Text = Counter.ToString(  );
      Counter++;
   }

   public int Counter
   {
      get 
      {
         if (ViewState["intCounter"] != null )
         {
            return ((int)ViewState["intCounter"]);
         }
         return 0;
     }

     set
     {
        ViewState["intCounter"] = value;
     }
   }
</script>

   <body>
   <form runat="server">

      <h1>ASP.NET State</h1>
      <br/>
      <h2>csStateBagDemo.aspx</h2>
      <br/>

      Counter:
      <asp:Label
         id="lblCounter"
         runat="server" />

      <asp:Button
         id="btn"
         text = "Increment Counter"
         runat="server" />

   </form>
   </body>
</html>
Example 6-6. Using the StateBag using VB.NET, vbStateBagDemo.aspx
<%@ Page Language="VB" Strict="true" %>
<html>

<script runat="server">

   sub Page_Load(ByVal Sender as Object, _
                 ByVal e as EventArgs) 
      lblCounter.Text = Counter.ToString(  )
      Counter += 1
   end sub

   public property Counter(  ) as integer
      get
         if not (ViewState("intCounter") is Nothing) then
               return CInt(ViewState("intCounter"))
         else
            return 0
         end if
      end get

      set
         ViewState("intCounter") = value
      end set
   end property
</script>

   <body>
   <form runat="server">

      <h1>ASP.NET State</h1>
      <br/>
      <h2>vbStateBagDemo.aspx</h2>
      <br/>
      
      Counter:
      <asp:Label
         id="lblCounter"
         runat="server" />

      <asp:Button
         id="btn"
         text = "Increment Counter"
         runat="server" />

   </form>
   </body>
</html>

In both Example 6-5 and Example 6-6, a Counter property is created that returns an integer. In C#, shown in Example 6-5, this is implicit in the class constructor by the use of the get and set keywords. In VB.NET, shown in Example 6-6, the Property keyword makes this explicit.

In the get block, the contents of the state bag named intCounter are tested to see if anything is there. In C#, this is accomplished with the line:

if (ViewState["intCounter"] != null )

In VB.NET, this is accomplished with the line:

if not (ViewState("intCounter") is Nothing) then

If the intCounter state bag is empty, then zero is returned. Otherwise, the value is retrieved and returned. The state bag returns an object that is not implicitly recognized as an integer so it must be cast as an integer before the method returns the value. In C# the syntax is:

return ((int)ViewState["intCounter"]);

In VB.NET, the code is:

return CInt(ViewState("intCounter"))

In the VB.NET code examples, the Page directive includes the Strict attribute:

<%@ Page Language="VB" Strict="true" %>

This attribute tells the compiler to disallow any implicit narrowing data conversion, which could possibly result in data loss. In these cases, you must explicitly cast (that is, convert, from one data type to another).

If the Strict attribute were omitted or set to false (its default value), then your code would not be forced to use type-safe behavior, allowing late binding of variable types. While on the one hand this would be a convenience, it would also represent a significant performance hit. If the Strict attribute were set to false, you would not have to cast your objects explicitly. You could replace the following lines:

if not ViewState("intCounter") is Nothing then
    return CInt(ViewState("intCounter"))

with these lines:

if (ViewState("intCounter") <> Nothing ) then
   return ViewState("intCounter")

In the set block, the intCounter value is set. In C#, the code is:

ViewState["intCounter"] = value;

In VB.NET, it is:

ViewState("intCounter") = value

In this code, value is a keyword used in the property set block to represent the implicit variable containing the value being passed in.

Then in the Page_Load, Counter is called twice—once to retrieve the counter value in order to set the value of the Label control's Text property and once to increment itself. In C# this is done with:

lblCounter.Text = Counter.ToString(  );
Counter++;

In VB.NET, this is done with:

lblCounter.Text = Counter.ToString(  )
Counter += 1

6.2.3 Application State

A web application consists of all the web pages, files, components, code, and images that reside in a virtual directory or its subdirectories.

The file global.asax contains global code for the web application. The global.asax file resides in the virtual root directory of the application. Chapter 20 discusses this file in detail. For now, only the aspects relating to application state and session state will be covered.

Among other things, the global.asax file contains event handlers for the Application_Start, Application_End, Session_Start, and Session_End events. When the application receives the first user request, the Application_Start event is fired. If the global.asax file is edited and the changes are saved, then all current pending requests are completed, the Application_End event is fired, and the application is restarted. This sequence effectively reboots the application, flushing all state information. The rebooting of the application is transparent to any users, however, since it occurs only after satisfying any pending requests and before any new requests are accepted. When the next request is received, the application starts over again raising another Application_Start event.

Information can be shared globally across your application via a dictionary of objects, each object associated with a key value. This is implemented using the intrinsic Application property of the HttpApplication class. The Application property allows access to the Contents collection, whose contents have been added to the Application state directly through code.

Example 6-7 and Example 6-8 each show a global.asax file, using C# and VB.NET, respectively.

Example 6-7. A global.asax file using C#
<%@ Application  Language="C#"%>
<script runat="server">

   protected void Application_Start(Object sender, EventArgs e)
   {
      Application["strStartMsg"] =  "The application has started.";
      Application["strDSN"] = 
               "SERVER=Zeus;DATABASE=Pubs;UID=sa;PWD=secret;";

      string[] Books = {"SciFi","Novels", "Computers", 
                        "History", "Religion"};
      Application["arBooks"] = Books;

      WriteFile("Application Starting");   
   }

   protected void Application_End(Object sender, EventArgs e)
   {    
      Application["strEndMsg"] =  "The application is ending.";
      WriteFile("Application Ending");
   }

   void WriteFile(string strText)
   {
      System.IO.StreamWriter writer = new 
                        System.IO.StreamWriter(@"C:\test.txt",true);
      string str;
      str = DateTime.Now.ToString(  ) + "  " + strText;
      writer.WriteLine(str);
      writer.Close(  );
   }
</script>
Example 6-8. A global.asax file using VB.NET
<%@ Application  Language="VB"%>
<script runat="server">

   protected sub Application_Start(ByVal Sender as Object, _
                                   ByVal e as EventArgs)
      Application("strStartMsg") =  "The application has started."
      Application("strDSN") = _
                    "SERVER=Zeus;DATABASE=Pubs;UID=sa;PWD=secret;"

      dim Books(  ) as string = {"SciFi","Novels", "Computers", _
                                "History", "Religion"}
      Application("arBooks") = Books

      WriteFile("Application Starting")
   end sub

   protected sub Application_End(ByVal Sender as Object, _
                                 ByVal e as EventArgs)
      Application("strEndMsg") =  "The application is ending."
      WriteFile("Application Ending")
   end sub

   sub WriteFile(strText as string)
      dim writer as System.IO.StreamWriter = new _
                        System.IO.StreamWriter("C:\\test.txt",true)
      dim str as string
      str = DateTime.Now.ToString(  ) & "  " & strText
      writer.WriteLine(str)
      writer.Close(  )
   end sub

</script>

A global.asax file is very similar to a normal .aspx file in that there is a directive on the first line followed by a script block in the language specified in the directive. In these cases, the directive is not the Page directive of a normal page, but an Application directive. In C#, these two lines look like this:

<%@ Application  Language="C#"%>
<script runat="server">

In VB.NET, the lines are:

<%@ Application  Language="VB"%>
<script runat="server">

You can see that the file has two event handlers—one each for Application_Start and Application_End. To see how this works, create a file called global.asax in your application virtual directory. (This book has been using the physical directory e:\Projects\Programming ASP.NET assigned to the virtual directory ProgAspNet for all the examples so far.) Depending on your language preference, edit the file so that it contains the code of either Example 6-7 or Example 6-8.

There can only be a single global.asax file in any application virtual directory. Each global.asax file must utilize a single language.

As mentioned previously, every time the global.asax file is modified, the .NET Framework detects this and automatically stops and restarts the application.

Now, run any of the web pages in that virtual directory, such as one of the examples from earlier in this chapter. At the instant the server receives and begins to process the page request, the application starts and the Application_Start event handler is called.

If you now open another browser and call some other .aspx file located in the same virtual directory, the application doesn't start again; it is already running. In fact, closing all your browsers and then opening a page will still not fire the Application_Start event. The application must first be ended, as described later in the explanation for Example 6-11.

Here is the Application_Start method in C#, reproduced from Example 6-7:

protected void Application_Start(Object sender, EventArgs e)
{
   Application["strStartMsg"] =  "The application has started.";
   Application["strDSN"] = 
            "SERVER=Zeus;DATABASE=Pubs;UID=sa;PWD=secret;";

   string[] Books = {"SciFi","Novels", "Computers", 
                     "History", "Religion"};
   Application["arBooks"] = Books;

   WriteFile("Application Starting");   
}

The Application property exposes a dictionary of objects linked to keys. In the Application_Start event handler in Example 6-7 and Example 6-8, three objects are entered in the Application dictionary: two strings and one string array. Then a call is made to the WriteFile method, which is coded further down in the script block. WriteFile writes a simple text log to the root of Drive C. If the file does not exist, it is created, and, if it does exist, the strings are appended to the end of the file.

Finally, the Application_End event handler of global.asax puts another string object in the Application dictionary, then makes a log entry.

The web pages in Example 6-9 and Example 6-10 show how these Application dictionary entries are used as global variables. Although the global.asax file is an excellent place to initialize global Application objects, it is not the only place. Application objects can be set from anywhere in the application, including any web page or code-behind file. The benefit of using the global.asax file is that you can be certain the global application objects will be set when the application first starts, regardless of which component of the application is accessed first. On the other hand, if the application design is such that a specific web page is always accessed first, then it would be perfectly reasonable to have that web page, or its associated code-behind file, perform any initialization.

Example 6-9. Application state example using C#, csApplicationState.aspx
<%@ Page Language="C#" %>
<html>

<script runat="server">

   void Page_Load(Object Source, EventArgs E)
   {
      Response.Write((string)Application["strStartMsg"] + "<br/>");
      Response.Write((string)Application["strDSN"] + "<br/>");
      Response.Write((string)Application["strEndMsg"]);

      string[] arTest = (string[])Application["arBooks"];
      Response.Write(arTest[1].ToString(  ));   }

</script>

   <body>
   <form runat="server">

      <h1>Application State Demo</h1>
      <br/>
      <br/>

   </form>
   </body>
</html>
Example 6-10. Application state example using VB.NET, vbApplicationState.aspx
<%@ Page Language="VB" Strict="true" %>
<html>

<script runat="server">

   sub Page_Load(ByVal Sender as Object, _
                 ByVal e as EventArgs)
      Response.Write(Cstr(Application("strStartMsg")) & "<br/>")
      Response.Write(Cstr(Application("strDSN")) & "<br/>")
      Response.Write(Cstr(Application("strEndMsg")))

      dim arTest(  ) as string = CType(Application("arBooks"), String(  ))
      Response.Write(arTest(1))

   end sub

</script>

   <body>
   <form runat="server">

      <h1>Application State Demo</h1>
      <br/>
      <br/>
      
   </form>
   </body>
</html>

The Application dictionary objects are retrieved as any other property would be, then cast to the appropriate type for use in the Response.Write method.

As mentioned previously in Section 6.2.2, the VB.NET Page directive in Example 6-10 has set Strict equal to true, which enforces Option Strict. If this were omitted or set to false, then it would not be necessary to explicitly cast the type:

dim arTest(  ) as string = _ CType(Application("arBooks"), string(  ))

Instead you could use this line:

dim arTest(  ) as string = _ Application("arBooks")

(The CType function is used in VB.NET to generically convert any expression to any type.)

Note that for backward compatibility with traditional ASP, you can refer to the Contents subproperty of the Application object. Thus, the following two lines of C# code are equivalent:

Response.Write((string)Application["strDSN"] + "<br/>");
Response.Write((string)Application.Contents["strDSN"] + "<br/>");

The following two lines of VB.NET code are also equivalent:

Response.Write(Cstr(Application("strDSN")) & "<br/>")
Response.Write(Cstr(Application.Contents("strDSN")) & "<br/>")

The results of running csApplicationState.aspx are shown in Figure 6-2.

Figure 6-2. Application state demo
figs/pan2_0602.gif

The application ends whenever global.asax is edited. (It also ends when IIS or the physical server is restarted, or when one of the application configuration files, such as web.config, is edited. Chapter 20 discusses the use of these configuration files.) Furthermore, the results of this effective rebooting of the application is invisible to the end users, since all pending requests are filled before the application shuts down. This can be seen if you force the application to end by making a minor change to global.asax and saving the file, then looking at the resulting log file, c:\test.txt, in Notepad, as shown in Example 6-11.

Example 6-11. Test.txt
5/25/2001 11:09:59 AM  Application Starting
5/25/2001 11:10:41 AM  Application Starting
5/25/2001 11:10:57 AM  Application Ending
5/25/2001 11:11:22 AM  Application Starting
5/25/2001 11:13:32 AM  Application Ending
5/25/2001 11:13:47 AM  Application Starting
5/25/2001 2:37:18 PM  Application Ending
5/25/2001 2:53:23 PM  Application Starting
5/25/2001 2:55:51 PM  Application Ending
5/25/2001 2:55:54 PM  Application Starting
5/25/2001 3:27:13 PM  Application Ending
5/25/2001 3:35:14 PM  Application Starting
5/25/2001 3:37:05 PM  Application Ending

As soon as any page in the virtual directory is accessed by a browser, another line appends itself to the log, containing the words Application Starting. However, you will never see the contents of the strEndMsg Application property (which was set in the Application_End event handler of global.asax as shown in Example 6-7 or Example 6-8) displayed in your browser because the application always ends between browser requests.

When using the application state, keep in mind the following considerations:

Concurrency and application locking

Concurrency refers to two or more pages accessing the same Application dictionary object simultaneously. As long as an Application dictionary object is read-only, this is not a problem. However, if you are going to allow clients to modify objects held in application state, then great care must be exercised (you'll see why in a moment). You must use the Lock and Unlock methods of the HttpApplicationState class to control access to the application state objects. If you fail to lock the application state object, one client may corrupt the data used by a second client. For example, consider the following code snippet in VB.NET, which increments an Application dictionary object called Counter:

Dim iCtr As Integer = CInt(Application("Counter"(  )
iCtr += 1
Application("Counter") = iCtr

It is possible for this code to be called by two clients at just about the same time. This code works by reading the Application ("Counter") variable, adding 1 to it, and writing it back. Suppose that client A and B both read the counter when its value is 5. Client A increments and writes back 6. Client B increments and also writes back 6, which is not what you want—you've lost track of Client A's increment. If you are keeping track of inventory, that would be a serious bug. You can solve this problem by locking and unlocking the critical code:

Application.Lock
Dim iCtr As Integer = CInt(Application("Counter"(  )
iCtr += 1
Application("Counter") = iCtr
Application.Unlock

Now when Application A reads the counter, it locks it. When Application B comes along, it is blocked by the lock until A unlocks. Thus, the value is properly incremented at the cost of a potential performance bottleneck.

You should always call the Unlock method as soon as possible to prevent blocking other users. If you forget to call Unlock, the lock will be automatically removed by .NET when the request completes or times out, or when an unhandled error occurs that causes the request to fail, thus minimizing prolonged deadlocks.

Simple locks like this are fraught with danger. For example, suppose that you have two resources controlled by locks: Counter and ItemsOnHand. Application A locks Counter and then tries to lock ItemsOnHand. Unfortunately, ItemsOnHand is locked, so A must wait, holding its lock on Counter. It turns out that Application B is holding the lock on ItemsOnHand waiting to get the lock on Counter. Application B must block waiting for A to let go of Counter, while A waits for B to let go of ItemsOnHand. This is called a deadlock or a deadly embrace. It is deadly to your application, which grinds to a halt.

Locks are particularly dangerous with web applications that have to scale up quickly. Use application locking with extreme caution. By extension, you should also use read-write application state with extreme caution.

Scalability

The issue of concurrency has a direct effect on scalability. Unless all the Application dictionary objects are read-only, you are liable to run into severe performance issues as the number of simultaneous requests increases, due to locks blocking other processes from proceeding.

Memory

This is a consideration for scalability also, since every Application dictionary object takes up memory. Whether you have a million short string objects or a single DataSet that takes up 50 MB, you must be cognizant of the potential memory usage of Application state.

Persistence and survivability

Application state objects will not survive if the application is halted—whether intentionally because of updates to global.asax or a planned shutdown, or because of unanticipated system crashes (when is a crash ever anticipated?). If it is important to persist a global application state, then you must take some measure to save it, perhaps to a database or other permanent file on disk.

Expandability to web farms and web gardens

The Application state is specific to a single process on a single processor. Therefore, if you are running a web farm (multiple servers) or a web garden (multiple processors in a single server), then any global values in the Application state will not be global across all the servers or processors, and so will not be truly global. As with persistence and survivability, if this is an issue, then you should get and set the value(s) from a central store accessible to all the processes, such as a database or data file.

One additional way of providing information globally across the application is through the use of static objects. These objects are declared in the global.asax file, described more fully in Chapter 20. Once declared with the Scope attribute set to Application, the objects are accessible by name anywhere within the application code.

6.2.4 Session State

While an application is running, there will be many sessions. A session is a series of requests coming from a single browser client in a more or less continuous manner. If there are no requests from that client within a specified period of time (the timeout period), then the session ends. The default timeout period is 20 minutes.

As has been stated before, the Web is an inherently stateless environment. The HTTP protocol has no means of identifying which requests should be grouped together as belonging to the same session. A session must be imposed on top of HTTP. ASP.NET provides session-state with the following features:

  • Works with browsers that have had cookies disabled.

  • Identifies if a request is part of an existing session.

  • Stores session-scoped data for use across multiple requests. This data persists across IIS restarts and works in multi-processor (web garden) and multi-machine (web farm) environments, as well as in single-processor, single-server situations.

  • Raises session events such as Session_Start and Session_End, which can be handled either in the global.asax file or in other application code.

  • Automatically releases session resources if the session ends or times out.

Session state is stored in server memory separately from the ASP.NET process. This means that if the ASP.NET process crashes or is restarted, the session state is not lost.

Sessions are identified and tracked with a 120-bit SessionID that is passed from client to server and back using either an HTTP cookie or a modified URL, depending on how the application is configured. The SessionID is handled automatically by the .NET Framework; there is no need to manipulate it programmatically. The SessionID consists of URL-legal ASCII characters that have two important characteristics:

  • They are unique, so that there is no chance of two different sessions having the same SessionID.

  • They are random, so that it is difficult to guess the value of another session's SessionID after learning the value of an existing session's SessionID.

Session state is implemented using the Contents collection of the HttpSessionState class. This collection is a key-value dictionary containing all the session state dictionary objects that have been directly added using code. The dictionary objects are set and retrieved using the Session keyword, as shown in Example 6-12 (using C#) and Example 6-13 (using VB.NET). These examples present a set of radio buttons. Selecting one of the radio buttons and clicking on the Submit button sets three session dictionary objects—two strings and a string array. These session dictionary objects are then used to populate a label control and a drop-down list control.

Example 6-12. Session state using C#, csSessionState.aspx
<%@ Page Language="C#" %>
<script runat="server">
   void btn_Click(Object Source, EventArgs E)
   {
      if (rbl.SelectedIndex == -1)
      {
         lblMsg.Text = "You must select a book category.";
      }
     else
     {
         StringBuilder sb = new StringBuilder(  );
         sb.Append("You have selected the category ");
         sb.Append((string)Session["cattext"]);
         sb.Append(" with code \"");
         sb.Append((string)Session["catcode"]);
         sb.Append("\".");

         lblMsg.Text = sb.ToString(  );

         ddl.Visible = true;

         string[] CatBooks = (string[])Session["books"];

         //  Populate the DropDownList.
         int i;
         ddl.Items.Clear(  );
         for (i = 0; i < CatBooks.GetLength(0); i++)
         {
            ddl.Items.Add(new ListItem(CatBooks[i]));
         }
     }
   }

   void rbl_SelectedIndexChanged(Object Source, EventArgs E)
   {
      if (rbl.SelectedIndex != -1)
      {
         string[] Books = new string[3];

         Session["cattext"] = rbl.SelectedItem.Text;
         Session["catcode"] = rbl.SelectedItem.Value;

         switch (rbl.SelectedItem.Value)
       {
       case "n":
            Books[0] = "Programming C#";
            Books[1] = "Programming ASP.NET";
            Books[2] = "C# Essentials";

            break;

       case "d":
            Books[0] = "Oracle & Open Source";
            Books[1] = "SQL in a Nutshell";
            Books[2] = "Transact-SQL Programming";

            break;

       case "h":
            Books[0] = "PC Hardware in a Nutshell";
            Books[1] = "Dictionary of PC Hardware and Data Communications Terms";
            Books[2] = "Linux Device Drivers";

            break;
       }
        
        Session["books"] = Books;
      }
   }
</script>

<html>
   <body>
   <form runat="server">

      <h1>Session State Demo</h1>
      <br/>
      <br/>

      <h3>Select a Book Category</h3>
      <asp:radioButtonList
         id="rbl"
         autoPostBack="false"
         cellSpacing="20"
         repeatColumns="3"
         repeatDirection="horizontal"
         RepeatLayout="table"
         textAlign="right"
         onSelectedIndexChanged="rbl_SelectedIndexChanged"
         runat="server">

         <asp:listItem text=".NET" value="n"/>  
         <asp:listItem text="Databases" value="d"/>  
         <asp:listItem text="Hardware" value="h"/>  

      </asp:radioButtonList>

      <asp:button
         id="btn"
         text="Submit"
         onClick="btn_Click" 
         runat="server"/>

      <br/>
      <br/>
      <hr/>
      
      <asp:label
         id="lblMsg"
         runat="server"/>

      <br/>
      <br/>

      <asp:dropDownList
         id="ddl"
         autoPostBack="false"
       visible= "false"
         runat="server"/>
      
   </form>
   </body> 
</html>

Example 6-13 shows only the Visual Basic script block, since the HTML portion of the .aspx file is identical to that in Example 6-12.

Example 6-13. Session state example using VB.NET, vbSessionState.aspx
<%@ Page Language="VB" Strict="true"%>
<script runat="server">

   sub btn_Click(ByVal Sender as Object, _
                 ByVal e as EventArgs)
      if (rbl.SelectedIndex = -1) then
         lblMsg.Text = "You must select a book category."
      else
         dim sb as StringBuilder = new StringBuilder(  )
         sb.Append("You have selected the category ")
         sb.Append(Cstr(Session("cattext")))
         sb.Append(" with code """)
         sb.Append(Cstr(Session("catcode")))
         sb.Append(""".")

         lblMsg.Text = sb.ToString(  )

         ddl.Visible = true

         dim CatBooks(  ) as string= CType(Session("books"), string(  ))

         '  Populate the DropDownList.
         dim i as integer
         ddl.Items.Clear(  )
         for i = 0 to CatBooks.GetLength(0) - 1
            ddl.Items.Add(new ListItem(CatBooks(i)))
         next
     end if
   end sub

   sub rbl_SelectedIndexChanged(ByVal Sender as Object, _
                                ByVal e as EventArgs)
      if (rbl.SelectedIndex <> -1) then

         dim Books(3) as string

         Session("cattext") = rbl.SelectedItem.Text
         Session("catcode") = rbl.SelectedItem.Value

         Select Case (rbl.SelectedItem.Value)
            Case "n"
               Books(0) = "Programming C#"
               Books(1) = "Programming ASP.NET"
               Books(2) = "C# Essentials"

            Case "d":
               Books(0) = "Oracle & Open Source"
               Books(1) = "SQL in a Nutshell"
               Books(2) = "Transact-SQL Programming"

            Case "h":
               Books(0) = "PC Hardware in a Nutshell"
               Books(1) = "Dictionary of PC Hardware and Data Communications Terms"
               Books(2) = "Linux Device Drivers"
       
         end select
         Session("books") = Books
      end if
   end sub
</script>

As usual, the first line of either the C# or the VB.NET .aspx file consists of a Page directive. In C#, this looks like:

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

In VB.NET, it looks like this:

<%@ Page Language="VB" Strict="true"%>

Jumping over the script block for a moment, the HTML contains a RadioButtonList and a Submit button on the top portion of the code, and a Label and an invisible DropDownList on the bottom portion of the code.

In the script block are two event handlers—one for trapping a change in value to the RadioButtonList, and one for catching the button click.

Look first at rbl_SelectedIndexChanged, the RadioButtonList event handler. This method populates the Session dictionary objects whenever the user selects a different radio button.

After testing to ensure that something is selected, rbl_SelectedIndexChanged defines a string array to hold the lists of books in each category. Then it assigns the selected item Text and Value properties to two Session dictionary objects. In C#, this is done as follows:

Session["cattext"] = rbl.SelectedItem.Text;
Session["catcode"] = rbl.SelectedItem.Value;

In VB.NET, the code is:

Session("cattext") = rbl.SelectedItem.Text
Session("catcode") = rbl.SelectedItem.Value

rblSelectedIndexChanged next uses a switch statement in C# or a Select Case statement in VB.NET to fill the previously declared string array with a list of books, depending on the book category selected.

Finally, the method assigns the string array to a Session dictionary object. In C#, this is done using:

Session["books"] = Books;

In VB.NET, the line is:

Session("books") = Books

This example stores only strings and an array in the session dictionary objects. However, you can store any object that inherits from ISerializable. These include all the primitive data types and arrays comprised of primitive data types, as well as the DataSet, DataTable, HashTable, and Image objects. This would allow you to store query results, for example, or a collection of items in a user's shopping cart.

The other event handler method, btn_Click, is called whenever the user clicks on the Submit button. It first tests to verify that a radio button has been selected. If not, then the Label is filled with a warning message. In C#, the code that does this is:

if (rbl.SelectedIndex == -1)
{
   lblMsg.Text = "You must select a book category.";
}

In VB.NET, the code is:

if (rbl.SelectedIndex = -1) then
   lblMsg.Text = "You must select a book category."

The else clause of the if statement is the meat of this page. It retrieves the session dictionary objects and uses the StringBuilder class to concatenate the strings together to make a single string for display in the Label control. In C#, this is done as follows:

StringBuilder sb = new StringBuilder(  );
sb.Append("You have selected the category ");
sb.Append((string)Session["cattext"]);
sb.Append(" with code \"");
sb.Append((string)Session["catcode"]);
sb.Append("\".");

lblMsg.Text = sb.ToString(  );

In VB.NET, this is done as follows:

dim sb as StringBuilder = new StringBuilder(  )
sb.Append("You have selected the category ")
sb.Append(Cstr(Session("cattext")))
sb.Append(" with code """)
sb.Append(Cstr(Session("catcode")))
sb.Append(""".")

lblMsg.Text = sb.ToString(  )

The btn_Click method also unhides the DropDownList that was created and made invisible in the HTML portion of the page. The method then retrieves the string array from the session dictionary object and populates the DropDownList. In C#, the code is:

ddl.Visible = true;

string[] CatBooks = (string[])Session["books"];

//  Populate the DropDownList.
int i;
ddl.Items.Clear(  );
for (i = 0; i < CatBooks.GetLength(0); i++)
{
   ddl.Items.Add(new ListItem(CatBooks[i]));
}

In VB.NET the code is:

ddl.Visible = true

dim CatBooks(  ) as string= CType(Session("books"), string(  ))

'  Populate the DropDownList.
dim i as integer
ddl.Items.Clear(  )
for i = 0 to CatBooks.GetLength(0) - 1
   ddl.Items.Add(new ListItem(CatBooks(i)))
next

Because the Page directive in the VB.NET example sets Strict="true", it is necessary to explicitly cast the session dictionary object containing the string array back to a string array using the CType function. The results of both the C# and VB.NET examples look the same, as shown in Figure 6-3.

Figure 6-3. Session state demo
figs/pan2_0603.gif

As you examine this example, you might wonder what advantage is gained here by using session state, rather than just using the programmatically accessible control values. The answer in this case is that since this particular example is fairly trivial, no advantage is gained. However, in a real life application with many different pages, session state provides an easy method for values and objects to be passed from one page to the next, with all the advantages listed at the beginning of this section.

6.2.4.1 Session state configuration

The configuration of session state is controlled on a page by page basis by entries in the Page directive at the top of the page. On an application-wide basis, it is controlled by a file called web.config, typically located in the virtual root directory of the application. (Page directives will be covered in detail later in this chapter, and configuration files will be covered in detail in Chapter 20.)

Session state is enabled by default. You can enable session state for a specific page by adding the EnableSessionState attribute to the Page directive, as in the following VB Page directive:

<%@ Page Language="VB" Strict="true" EnableSessionState="true"%>

To disable session state for the page you would use:

<%@ Page Language="VB" Strict="true" EnableSessionState="false"%>

To enable session state in a read-only mode—that is, values can be read but not changed—use the ReadOnly value of EnableSessionState, as in:

<%@ Page Language="VB" Strict="true" EnableSessionState="ReadOnly"%>

(All of the values for EnableSessionState are case-insensitive.) The reason for either disabling session state or making it read-only is performance. If you know that you will not be using session state on a page, you can gain a performance boost by disabling it.

Session state is stored in server memory separately from the ASP.NET process. This means that if the ASP.NET process crashes or is restarted, the session state is not lost. In addition to unplanned outages, ASP.NET can be configured to periodically perform a preventative restart of each process after a specified number of requests or after a specified length of time, improving availability and stability (this is configurable in machine.config and/or web.config. See Chapter 20 for a complete discussion of configuration.). Session state is preserved even across these restarts.

Keep in mind that web.config is an XML file and as such it must be well-formed. (Well-formed XML files are described in the sidebar Well-Formed HTML in Chapter 4.) The values are case-sensitive, and the file consists of sections delimited by tags. The session state configuration information is contained within the <system.web> section, which is contained within the <configuration> section. Thus, a typical session state configuration snippet will look something like Example 6-14.

Example 6-14. Code snippet from web.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    
  <system.web>
.
.
.
   <sessionState 
           mode="InProc"
           cookieless="false" 
           timeout="20" 
           stateConnectionString="tcpip=127.0.0.1:42424"
           sqlConnectionString="data source=127.0.0.1;userid=sa;password="
   />

There are five possible attributes for the sessionState section:

mode

Specifies whether the session state is disabled for all the pages controlled by this copy of web.config, and, if enabled, where the session state is stored. Table 6-4 lists the permissible values.

Table 6-4. Possible values for the mode attribute

Values

Description

Off

Session state is disabled.

Inproc

Session state is stored in process on the local server. This is the default value.

StateServer

Session state is stored on a remote server. If this attribute is used, then there must also be an entry for stateConnectionString, which specifies which server to use to store the session state.

SqlServer

Session state is stored on a SQL Server. If this attribute is used, then there must also be an entry for sqlConnectionString, which specifies how to connect to the SQL Server. The SQL Server used can either be on the local or a remote machine.

Storing the session state Inproc is the fastest and is well-suited to small amounts of volatile data. However, it is susceptible to crashes and is not suitable for web farms (multiple servers) or web gardens (multiple processors on a single machine). For these cases, you should use either StateServer or SqlServer. SqlServer is the most robust for surviving crashes and restarts.

cookieless

Cookies are used with session state to store the SessionID so that the server knows which session it is connected to. The permissible values of cookieless are true and false, with false being the default. In other words, the default behavior is to use cookies. However, if the client browser either does not support cookies or has had cookie support turned off by the user, then any attempt at saving and retrieving session state will be lost. To prevent this, set cookieless to true.

If cookieless is set to true, then the SessionID is persisted by adding a value to the URL, as shown in the address bar in Figure 6-4.

Figure 6-4. Session state demo in cookieless mode
figs/pan2_0604.gif
timeout

Specifies the number of minutes of inactivity before a session times out and is abandoned by the server. The default value is 20.

stateConnectionString

Specifies the server and port used to save the session state. It is required if mode is set to StateServer. Use of a specific server for saving state enables easy and effective session state management in web farm or web garden scenarios. An example of a stateConnectionString is:

stateConnectionString="tcpip=127.0.0.1:42424"

In this example, a server with an IP address of 127.0.0.1 would be used. This happens to be localhost, or the local machine. The port is 42424. In order for this to work, the server being specified must have the ASP.NET State service started (accessible via Control Panel/Administrative Tools/Services) and must have the specified port available for communications (that is, not disabled or blocked by a firewall or other security measure).

sqlConnectionString

Specifies a connection string to a running instance of SQL Server. It must be set if mode is set to SqlServer. Similar to stateConnectionString in that it lends itself to use with web farms and gardens, it also will persist despite crashes and shutdowns. The session state is saved in SQL tables indexed by SessionID.

6.2.4.2 Session scoped application objects

One additional way of providing information across the session is through the use of static objects, which are declared in the global.asax file (described in Chapter 20). Once declared with the Scope attribute set to Session, the objects are accessible by name to the session anywhere within the application code.

    Previous Section Next Section


    JavaScript Editor Javascript validator     Javascripts 




    ©