6.2 StateState 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:
Table 6-3 compares the different kinds of state.
The following sections will examine each type of state in turn. 6.2.1 View StateThe 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 BagIf 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 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++; lblCounter.Text = Counter.ToString( ) Counter += 1 6.2.3 Application StateA 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.
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.
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 demoThe 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.txt5/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:
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 StateWhile 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:
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:
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 demoAs 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 configurationThe 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:
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.
Figure 6-4. Session state demo in cookieless mode
6.2.4.2 Session scoped application objectsOne 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. |