Section 6.4. State

6.4. 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 re-created from scratch. Unless the state of all the controls is explicitly preserved 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 controlsboth HTML and ASP.NETso you do not have to write any code to accomplish this. This section will explore how this is done and how you can make use of the ASP.NET state management capabilities.

ASP.NET manages four types of state:

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

  • Control state

  • Application state

  • Session state

Control state, (described below in conjunction with View state), cannot be modified, accessed directly, or disabled. Table 6-5 compares the other kinds of state management.

Table 6-5. Comparison of types of state

Feature

View state

Application state

Session state

Uses server resources

No

Yes

Yes

Uses bandwidth

Yes

No

No

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 on configuration


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

6.4.1. Session State

When you connect to an ASP.NET web site, you create a session. The session imposes state on the otherwise stateless Web and allows the web site to recognize that subsequent page requests are from the same browser that started the session. This allows you to maintain state across pages until you consciously end the session or the session times out. (The default timeout is 20 minutes.)

While an application is running, there will be many sessions , essentially , one for each user interacting with the web site.

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 can be configured to persist across IIS restarts and work 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.

By default, session state is stored in server memory as part of the ASP.NET process. However, as will be shown shortly, it can be configured to be stored separately from the ASP.NET process, either on a separate state server or in a SQL Server database, in which case it will survive a crash or restart of the ASP.NET process.

Sessions are identified and tracked with a 120-bit SessionID that is passed from client to server and back using 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 globally unique so there is no chance of two different sessions having the same SessionID.

  • They are random so 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 property of the HttpSessionState class . This collection is a key-value (non-generic) dictionary containing all the session state dictionary objects that have been directly added programatically . The dictionary objects are set and retrieved using the Session keyword, as shown in the following example, SessionStateDemo.

This example presents a set of radio buttons. Selecting one of the radio buttons and clicking the Submit button sets three session dictionary objectstwo strings and a string array . These session dictionary objects are then used to populate a label control and a drop-down list control.

To create this example, drag a RadioButtonList , a Button , a Label , and an invisible DropDownList control onto the page, along with some HTML to spread things out a bit. The content file is listed in Example 6-16.

Example 6-16. Default.aspx for SessionStateDemo
 <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs"     Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server">     <title>Session State</title> </head> <body>     <form id="form1" runat="server">     <div>       <h1>Session State</h1>       <h3>Select a book category</h3>  <asp:RadioButtonList ID="rbl" runat="server"             CellSpacing="20"             RepeatColumns="3"             RepeatDirection="Horizontal"             OnSelectedIndexChanged="rbl_SelectedIndexChanged">           <asp:ListItem Value="n">.NET</asp:ListItem>           <asp:ListItem Value="d">Databases</asp:ListItem>           <asp:ListItem Value="h">Hardware</asp:ListItem>        </asp:RadioButtonList>        <asp:Button ID="btn" runat="server"             Text="Submit"             OnClick="btn_Click" />  <br />        <br />  <asp:Label ID="lblMessage" runat="server"></asp:Label>  <br />        <br />  <asp:DropDownList ID="ddl" runat="server" Visible="False">        </asp:DropDownList>  </div>     </form> </body> </html> 

In the code-behind file are two event handlers, one for the SelectedIndexChanged event of the RadioButtonList and one for the Click event of the Button . A using System.Text statement is required at the beginning of the file to enable use of the StringBuilder . The complete code-behind file is shown in Example 6-17.

Example 6-17. Default.aspx.cs for SessionStateDemo
 using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls;  using System.Text;         //  necessary for StringBuilder  public partial class _Default : System.Web.UI.Page {     protected void Page_Load(object sender, EventArgs e)     {     }  protected void rbl_SelectedIndexChanged(object sender, 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;        }     }   protected void btn_Click(object sender, EventArgs e)     {        if (rbl.SelectedIndex == -1)        {           lblMessage.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("\".");           lblMessage.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]));           }        }     }  } 

Look first at rbl_SelectedIndexChanged , the RadioButtonList event handler in Example 6-17. 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.

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

rblSelectedIndexChanged next uses a switch statement 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.

 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, and any user-created classes that implement the interface. This allows 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 the Submit button. It tests to verify that a radio button has been selected. If not, the Label will be filled with a warning message.

 if (rbl.SelectedIndex == -1) {    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.

 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(); 

The btn_Click method unhides the DropDownList that was created and made invisible in the content file of the page. The method then retrieves the string array from the Session dictionary object and populates the DropDownList :

 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])); } 

As you examine this example, you might wonder what advantage is gained here by using session state , rather than using the programmatically accessible control values. In this trivial example, 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.4.1.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 the web.config configuration file, 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 18.)

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

 <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs"     Inherits="_Default" EnableSessionState="True"%> 

To disable session state for the page, you would use the following:

 <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs"     Inherits="_Default" EnableSessionState="False"%> 

To enable session state in a read-only modei.e., values can be read but not changeduse the ReadOnly value of EnableSessionState , as in the following:

 <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs"     Inherits="_Default" EnableSessionState="ReadOnly"%> 

(All of the values for EnableSessionState are case-insensitive.) The reason for disabling session state or making it read-only is to enhance performance . If you know that you will not be using session state on a page, you can gain a small performance boost and conserve server resources at the same time by disabling session state.

By default, session state is stored in server memory as part of the ASP.NET process. However, using the mode attribute of the sessionState tag in web.config , it can be configured to be stored separately from the ASP.NET process, either on a separate state server or in a SQL Server database, in which case it will survive a crash or restart of the ASP.NET process. In addition to unplanned outages, ASP.NET can be configured to perform a preventative restart periodically 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 18 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 XHTML" in Chapter 3.) The values are case-sensitive, and the file consists of sections delimited by tags .


Within web.config , the session state configuration information is contained in the <system.web> section, which itself is contained within the <configuration> section. Thus, a typical session state configuration snippet will look something like Example 6-18.

Example 6-18. Session State 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 several possible attributes, all optional, for the sessionState section:



allowCustomSqlDatabase

If TRue , the SQL database storing session data can be a custom database. The default is false , in which case the default database is ASPState and an Initial Catalog cannot be specified in the connection string.



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-6 lists the permissible values.

Storing the session state Inproc is the fastest method and is well-suited to small amounts of volatile data. However, it is susceptible to crashes and is unsuitable 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 the server knows which session the request is connected to. The permissible values of cookieless are AutoDetect , UseCookies , UseDeviceProfile , and UseUri , with UseCookies being the default.

AutoDetect will check to see if the requesting client supports cookies. UseDevice-Profile will determine if cookies are supported based on HttpBrowserCapabili-ties . If either of these determine that cookies are unsupported or if UseUri is specified, then the SessionID will be persisted by adding a value to the URL, as shown in the address bar in Figure 6-5. A value of cookieless that forces the SessionID to be added to the URL will not work within the VS2005 environment if using File System access but will work outside VS2005 using a virtual directory.



cookieName

The name of the cookie that stores the SessionID . The default is ASP.NET_SessionId .



customProvider

The name of the custom session state provider.



regenerateExpiredSessionId

For use with cookieless sessions. If TRue , expired SessionID 's are replaced with a new identifier. The default is false .



sqlCommandTimeout

The number of seconds a SQL command is idle before being canceled . The default value is 30 .



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 will persist despite crashes and shutdowns. The session state is saved in SQL tables indexed by SessionID .



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. Here is an example of a stateConnectionString :

 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 (the local machine ). The port is 42424 . 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 (i.e., not disabled or blocked by a firewall or other security measure).



stateNetworkTimeout

Used when the mode is set to StateServer , the number of seconds the TCP/IP network connection can be idle before the request is canceled. The default is 10 .



timeout

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



useHostingIdentity

If true , which is the default, the ASP.NET process identity will be impersonated.

Table 6-6. 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 an entry must exist 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 an entry must exist for sqlConnectionString , which specifies how to connect to the SQL Server . The SQL Server used can be on the local or a remote machine.

Custom

Allows you to specify a custom provider.


Figure 6-5. SessionStateDemo with cookieless=UseUri

6.4.1.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 18). Once declared with the Scope attribute set to Session , the objects are accessible by name to the session anywhere within the application code.

6.4.2. 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) in a hidden field on the page that contains the state encoded in a string variable. Since the view state is maintained via form fields, this technique 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 will be 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"> ), but note carefully that doing so will 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 web.config configuration file, or for all applications in the machine.config configuration file (described in Chapter 18).

Maintaining or disabling the view state for specific controls is possible. 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 GridView is populated from a database every time the page is loaded. In this case, the contents of the control would be overridden by the database query, so there is no point in maintaining view state for that control. If the GridView in question were named gv , the following line of code would disable its view state:

 gv.EnableViewState = false; 

There are some situations where view state is not the best place to store data. If a large amount of data must be stored, view state is not an efficient mechanism since the data is transferred back and forth to the server with every page post. If security concerns exist about the data and the data is not 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.

In Version 2.0 of ASP.NET, the serialization format for view state has been reformulated, cutting the size of the hidden field that is embedded in the page by as much as 50 percent compared to ASP.NET 1.x.

The second change is perhaps more useful. In Version 1.x, many controls used view state to store data required for functionality as well as data display. For example, sorting and paging of the DataGrid required that view state be enabled. If you disabled view state, you disabled that functionality. Version 2.0 separates out the functional data from the display data and stores the former in a new category called control state . Control state cannot be disabled, so even if view state is disabled, the control will still function correctly.


6.4.3. State Bag

If values 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 typesintegers, bytes, strings, Booleans, and so on. The state bag is implemented using the StateBag class, which is a (non-type-safe) 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# , Fourth Edition, by Jesse Liberty (O'Reilly).

The state bag is maintained using the same hidden field as view state. You can set and retrieve values of things in the state bag using the ViewState keyword, as shown in the following example, StateBagDemo.

This example sets up a counter that is maintained as long as the page is current. Every time the Increment Counter button is clicked, the page is reloaded, which causes the counter to increment.

Create StateBagDemo by creating a new web site in VS2005. Drag a Label control, named lblCounter , and a Button control, named btn , onto the page. The listing for the content file is shown in Example 6-19.

Example 6-19. default.as for StateBagDemo
 <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs"    Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server">     <title>State Bag</title> </head> <body>     <form id="form1" runat="server">     <div>       <h1>State Bag</h1>       Counter:        <asp:Label ID="lblCounter" runat="server" />        <asp:Button ID="btn" runat="server" Text="Increment Counter" />     </div>     </form> </body> </html> 

The code-behind creates a property of type integer, called Counter . The contents of Counter is stored in the state bag using the ViewState property of the Page class. In the Page_Load method, the Counter property is assigned to the Label and then incremented. Since all the button is doing is submitting the form, it does not require an event handler. The complete code-behind is shown in Example 6-20, with the Counter property highlighted.

Example 6-20. Default.aspx.cs for StateBagDemo
 using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class _Default : System.Web.UI.Page {     protected 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;       }    }  } 

In the get block of the Counter property, the contents of the state bag named intCounter are tested to see if anything is there.

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

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.

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

In the set block, the intCounter value is set.

 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 to set the value of the Label control's Text property and once to increment itself.

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

6.4.4. 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 18 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 , Application_Error , 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 will be completed, the Application_End event will be fired, and the application will be restarted. This sequence effectively reboots the application, flushing all state information. However, the rebooting of the application is transparent to all users 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.

To add a global.asax file to a project, click Website Add New Item... (or right-click the project root directory in the Solution Explorer and select Add New Item...). From the Add New Item dialog box, select Global Application Class and accept the default name of Global.asax . The file will be created with empty event handler methods for the application and session events mentioned above.

Create a new web site in VS2005 and add a global.asax file to a project. To this file, add the highlighted code listed in Example 6-21.

Example 6-21. global.asax file in C#
 <%@ Application Language="C#" %> <script runat="server">     void Application_Start(Object sender, EventArgs e) {         // Code that runs on application startup  Application["strStartMsg"] = "The application has started.";        Application["strConnectionString"] =                 "SERVER=Zeus;DATABASE=Pubs;UID=sa;PWD=secret;";        string[] Books = {"SciFi","Novels", "Computers",                         "History", "Religion"};        Application["arBooks"] = Books;        WriteFile("Application Starting");  }     void Application_End(Object sender, EventArgs e) {         //  Code that runs on application shutdown  Application["strEndMsg"] = "The application is ending.";        WriteFile("Application Ending");  }     void Application_Error(Object sender, EventArgs e) {         // Code that runs when an unhandled error occurs     }     void Session_Start(Object sender, EventArgs e) {         // Code that runs when a new session is started     }     void Session_End(Object sender, EventArgs e) {         // Code that runs when a session ends.         // Note: The Session_End event is raised only when the sessionstate mode         // is set to InProc in the Web.config file. If session mode is set to StateServer         // or SQLServer, the event is not raised.     }  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> 

A global.asax file is similar to a normal .aspx file in that a directive is on the first line followed by a script block in the language specified in the directive. In this case, 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"> 

You can see that the global.asax file has two event handlers that actually have code to do something: one each for Application_Start and Application_End . In addition, it has a method called WriteFile , which uses a StreamWriter to write a simple log to a text file hardcoded to be in the root of the C drive.

There can only be one global.asax file in any application virtual directory.


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

You could copy this global.asax file into the virtual root of any web site and see it in action. However, for now, you will use ApplicationStateDemo, which you just created. The content page has no controls other than perhaps a heading. The content file is shown in Example 6-22.

Example 6-22. Default.aspx for ApplicationStateDemo
 <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs"    Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server">     <title>Application State</title> </head> <body>     <form id="form1" runat="server">     <div>        <h1>Application State</h1>     </div>     </form> </body> </html> 

In the Page_Load of the code-behind file, you will retrieve values from Application state and write them to the page using Response.Write . To do this, add the highlighted lines of code from Example 6-23 to the code-behind file.

Example 6-23. Default.aspx.cs from ApplicationStateDemo
 using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class _Default : System.Web.UI.Page {     protected void Page_Load(object sender, EventArgs e)     {  Response.Write((string)Application["strStartMsg"] + "<br/>");       Response.Write((string)Application["strConnectionString"] + "<br/>");       Response.Write((string)Application["strEndMsg"]);       string[] arTest = (string[])Application["arBooks"];       Response.Write(arTest[1].ToString());  } } 

The Application dictionary objects are retrieved by using the key value as an indexer into the dictionary, then casting the object returned to the appropriate type for use in the Response.Write method.

Run the application and you will see something like the screen shown in Figure 6-6.

Figure 6-6. ApplicationStateDemo

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 because 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 in the explanation for Example 6-24.

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

The Application property exposes a dictionary of objects linked to keys. In the Application_Start event handler, in Example 6-21, 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 file. WriteFile writes a text log to the root of the C drive. If the file does not exist it will be created, and if it does exist the strings will be appended to the end of the file.

For WriteFile to work, the ASP.NET user must have sufficient permissions to write a file to the specified location. See the description of the FileUpload control in Chapter 5 for a discussion of this issue.


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

ApplicationStateDemo shows how these Application dictionary entries are used as global variables. Though 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 will be perfectly reasonable to have that web page, or its associated code-behind file, perform any initialization.

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["strConnectionString"] + "<br/>"); Response.Write((string)Application.Contents["strConnectionString"] + "<br/>"); 

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 18 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-24.

As soon as any page in the virtual directory is requested 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-21) 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, exercise great care (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, which increments an Application dictionary object called Counter :

 int iCtr = (int)Application["Counter"]; iCtr++; Application["Counter"] = iCtr 

Two clients could possibly call this code at about the same time. This code works by reading the Application["Counter"] value, adding 1 to it, and writing it back. Suppose that clients A and B read the counter when its value is 5 . Client A increments and writes back 6. Client B increments and writes back 6, which is not what you want, and you've lost track of Client A's increment. If you were keeping track of inventory, that would be a serious bug. You can solve this problem by locking and unlocking the critical code:

 Application.Lock(); 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 , and 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 50MB, 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 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), any global values in the Application state will not be global across all the servers or processors and so will not be 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 18. Once declared with the Scope attribute set to Application , the objects are accessible by name anywhere within the application code.



Programming ASP. NET
Programming ASP.NET 3.5
ISBN: 0596529562
EAN: 2147483647
Year: 2003
Pages: 173

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net