Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1038 Chapter 22: State Management The Page has a public property aptly named Session that automatically retrieves the Session from the current HttpContext . Even though it seems as if the Session object lives inside the page, it actually lives in the HttpContext , and the page’s public Session property actually retrieves the reference to the session state. This convenience not only makes it more comfortable for the classic ASP programmer, but saves you a little typing as well. The Session object can be referred to within a page in this way: Session["SomeSessionState"] = "Here is some data"; or HttpContext.Current.Session["SomeSessionState"] = "Here is some data"; The fact that the Session object actually lives in the current HTTP context is more than just a piece of trivia. This knowledge enables you to access the Session object in contexts other than the page (such as in your own HttpHandler ). Configuring Session State Management All the code within a page refers to the Session object using the dictionary-style syntax seen previously, but the HttpSessionState object uses a Provider Pattern to extract possible choices for session state storage. You can choose between the included providers by changing the sessionState element in web.config . ASP.NET ships with the following three storage providers: ❑ In-Process Session State Store: Stores sessions in the ASP.NET in-memory cache. ❑ Out-Of-Process Session State Store: Stores sessions in the ASP.NET State Server service aspnet_state.exe . ❑ Sql Session State Store: Stores sessions in Microsoft SQL Server database and is configured w ith aspnet_regsql.exe . The format of the web.config file’s sessionState element is shown in the following code: < configuration > < system.web > < sessionState mode="Off|InProc|StateServer|SQLServer|Custom" / > < /system.web > Begin configuring session state by setting the mode="InProc" attribute of the sessionState element in the web.config of a new Web site. This is the most common configuration for session state within ASP.NET 2.0 and is also the fastest, as you see next. In-Process Session State When the configuration is set to InProc , session data is stored in the HttpRuntime’s internal cache in an implementation of ISessionStateItemCollection that implements ICollection . The session state key is a 120-bit value string that indexes this global dictionary of object references. When session state is in process, objects are stored as live references. This is an incredibly fast mechanism because no serialization 1038 Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1039 Chapter 22: State Management occurs, nor do objects leave the process space. Certainly, your objects are not garbage-collected if they exist in the In-Process Session object because a reference is still being held. Additionally, because the objects are stored (held) in memory, they use up memory until that session times out. If a user visits your site and hits one page, he might cause you to store a 40 MB XmlDocument in in-process session. If that user never comes back, you are left sitting on that large chunk of memory for the next 20 minutes or so (a configurable value) until the Session ends, even if the user never returns. InProc Gotchas Although the InProc Session model is the fastest, the default, and the most common, it does have a significant limitation. If the worker process or application domain recycles, all session state data is lost. Also, the ASP.NET application may restart for a number of reasons, such as the following: ❑ You’ve changed the web.config or Global.asax file or ‘‘touched’’ it by changing its modified date. ❑ You’ve modified files in the \ bin or \ App_Code directory. ❑ The processModel element has been set in the web.config or machine.config file indicating when the application should restart. Conditions that could generate a restart might be a memory limit or request-queue limit. ❑ Antivirus software modifies any of the previously mentioned files. This is particularly common with antivirus software that innoculates files. This said, In-Process Session State works great for smaller applications that require only a single Web server, or in situations where IP load balancing is returning each user to the server where his original Session was created. If a user already has a Session key, but is returned to a different machine than the o ne on which his session was created, a new Session is created on that new machine using the session ID supplied by the user. Of course, that new Session is empty and unexpected results may occur. However if regenerate- ExpiredSessionId is set to True in the web.config file, a new Session ID is created and assigned to the user. Web Gardening Web gardening is a technique for multiprocessor systems wherein multiple instances o f the ASP.NET worker process are started up and assigned with processor affinity. On a larger Web server with as many as four CPUs, you could have anywhere from one to four worker processes hosting ASP.NET. Processor affinity means literally that an ASP.NET worker process has an affinity for a particular CPU. It’s ‘‘pinned’’ to that CPU. This technique is usually enabled only in very large Web farms. Don’t forget that In-Process Session State is just that — in-process. Even if your Web application consists of only a single Web server and all IP traffic is routed to that single server, you have no guarantee that each subsequent request will be served on the same processor. A Web garden must follow many of the same rules that a Web farm follows. If you’re using Web gardening on a multiprocessor system, you must not use In-Process Session State or you lose Sessions. In-Process Session State is appropriate only where there is a 1:1 ratio of applications to application domains. 1039 Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1040 Chapter 22: State Management Storing Data in the Session Object In the following simple example, in a Button_Click event the content of the text box is a dded to the Session object with a specific key. The user then clicks to go to another page within the same application, and the data from the Session object is retrieved and presented in the browser. Note the use of the < asp:HyperLink > control. Certainly, that markup could have been hard-coded as HTML, but this small distinction will serve us well later. Additionally, the URL is relative to this site, not absolute. Watch for it to help you later in this chapter. Listing 22-1 illustrates how simple it is to use the Session object. It behaves like any other IDictionary collection and allows you to store keys of type String associated with any kind of object. The Retrieve .aspx file referenced will be added in Listing 22-2. Listing 22-1: Setting values in session state ASP.NET < %@ Page Language="C#" CodeFile="Default.aspx.cs" Inherits="_Default" % > ASP.NET — VB.NET < %@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" % > ASP.NET < !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 > < asp:TextBox ID="TextBox1" Runat="server" >< /asp:TextBox > < asp:Button ID="Button1" Runat="server" Text="Store in Session" OnClick="Button1_Click" / > < br / > < asp:HyperLink ID="HyperLink1" Runat="server" NavigateUrl="Retrieve.aspx" > Next Page < /asp:HyperLink > < /div > < /form > < /body > < /html > VB Partial Class _Default Inherits System.Web.UI.Page Protected Sub Button1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Session("mykey") = TextBox1.Text End Sub End Class 1040 Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1041 Chapter 22: State Management C# public partial class _Default : System.Web.UI.Page { protected void Button1_Click(object sender, EventArgs e) { Session["mykey"] = TextBox1.Text; } } The page from Listing 22-1 renders in the browser as shown in Figure 22-2. The Session object is accessed as any dictionary indexed by a string key. Figure 22-2 More details about the page and the Session object can be displayed to the developer if page tracing is enabled. You add this element to your application’s web.config fileinsidethe< system.web > element, as follows: < trace enabled="true" pageOutput="true"/ > Now tracing is enabled, and the tracing output is sent directly to the page. More details on tracing and debugging are given in Chapter 24. For now, make t his change and refresh your browser. In Figure 22-3, the screenshot is split to show both the top and roughly the middle of the large amount of trace information that is returned when trace is e nabled. Session State is very much baked into the fabric of ASP.NET. You can see in the Request Details section of the trace that not only was this page the result of an HTTP POST but the Session ID was as well — elevated to the status of first-class citizen. However, the ASP.NET Session ID lives as a cookie by default, as you can see in the Cookies collection at the bottom of the figure. The default name for that cookie is ASP.NET_SessionId , but its name can be configured via the cookieN- ame attribute of the < sessionState > element in web.config . Some large enterprises allow only certain named cookies past their proxies, so you might need to change this value when working on an extranet or a network with a gateway server; but this would be a very rare occurrence. The cookieName is changed to use the name ‘‘Foo’’ in the following example: < sessionState cookieName="Foo" mode="InProc" >< /sessionState > 1041 Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1042 Chapter 22: State Management The trace output shown in Figure 22-3 includes a section listing the contents of the Session State collection. In the figure, you can see that the name mykey and the value Hanselman are currently stored. Additionally, you see the CLR data type of the stored value; in this case, it’s System.String . Figure 22-3 The Value column of the trace output comes from a call to the contained object’s ToString() method. If you store your own objects in the Session, you can override ToString() to provide a text-friendly representation of your object that might make the trace results more useful. Now add the next page, retrieve.aspx , which pulls this value out of the session. Leave the retrieve .aspx page as the IDE creates it and add a Page_Load event handler, as shown in Listing 22-2. Listing 22-2: Retrieving values from the session VB Partial Class Retrieve Inherits System.Web.UI.Page 1042 Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1043 Chapter 22: State Management Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Load Dim myValue As String = CType(Session("mykey"), String) Response.Write(myValue) End Sub End Class C# public partial class Retrieve : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string myValue = (string)Session["mykey"]; Response.Write(myValue); } } Because the session co ntains object references, the resulting object is converted to a string by way of a cast in C# or the CType or CStr function in VB. Making Sessions Transparent It is unfortunate that a cast is usually required to retrieve data from the Session object. Combined with the string key used as an index, it makes for a fairly weak contract between the page and the Session object. You can create a session helper that is specific to your application to hide these details, or you can add properties to a base Page class that presents these objects to your pages in a friendlier way. Because the generic Session object is available as a property on System.Web.UI.Page , add a new class derived from Page that exposes a new property named MyKey . Start by right-clicking your project and selecting Add New Item from the context menu to create a new class. Name it SmartSessionPage and click OK. The IDE may tell you that it would like to put this new class in the /App_Code folder to make it available to the whole a pplication. Click Yes. Your new base page is very simple. Via derivation, it does everything that System.Web.UI.Page does, plus it has a new property, as shown in Listing 22-3. Listing 22-3: A more session-aware base page VB Imports Microsoft.VisualBasic Imports System Imports System.Web Public Class SmartSessionPage Inherits System.Web.UI.Page Private Const MYSESSIONKEY As String = "mykey" Public Property MyKey() As String Get Return CType(Session(MYSESSIONKEY), String) End Get 1043 Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1044 Chapter 22: State Management Set(ByVal value As String) Session(MYSESSIONKEY) = value End Set End Property End Class C# using System; using System.Web; public class SmartSessionPage : System.Web.UI.Page { private const string MYKEY = "mykey"; public string MyKey { get { return (string)Session[MYKEY]; } set { Session[MYKEY] = value; } } } Now, return to your code from Listing 22-1 and derive your pages from this new base class. To do this, change the base class in the code-beside files to inherit from SmartSessionPage . Listing 22-4 shows how the class in the code-behind file derives from the SmartSessionPage , which in turn derives from System.Web.UI.Page . Listing 22-4 outlines the changes to make to Listing 22-1. Listing 22-4: Deriving from the new base page VB — ASPX < %@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" % > VB — Default.aspx.vb Code Partial Class _Default Inherits SmartSessionPage Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) ’ Session("mykey") = TextBox1.Text MyKey = TextBox1.Text End Sub End Class C# — ASPX < %@ Page Language="C#" CodeFile="Default.aspx.cs" Inherits="_Default" % > C# — Default.aspx.cs Code public partial class _Default : SmartSessionPage 1044 Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1045 Chapter 22: State Management { protected void Button1_Click(object sender, EventArgs e) { //Session["mykey"] = TextBox1.Text; MyKey = TextBox1.Text; } } In this code, you change the access to the Session object so it uses the new public property. After the changes in Listing 22-3, all derived pages have a public property called MyKey . This property can be used without any concern about casting or Session key indexes. Additional specific properties can be added as other objects are included in the Session. Here’s an interesting language note: In Listing 22-3 the name of the private string value collides with the public property in VB because they differ only in case. In C#, a private variable named MYKEY and a public property named MyKey are both acceptable. Be aware of things like this w hen creati ng APIs that will be used with multiple languages. Aim for CLS compliance. Advanced Techniques for Optimizing Session Performance By default, all pages have write access to the Session . Because it’s possible that more than one page from the same browser client might be requested at the same time (using frames, more than one browser window on the same machine, and so on), a page holds a reader/writer lock on the same Session for the duration of the page request. If a page has a writer lock on the same Session , all other pages requested in the same Session must wait until the first request finishes. To be clear, the Session is locked only for that SessionID. These locks don’t affect other users with different Sessions . In order t o get the best performance out of your pages that use Session , ASP.NET allows you declare exactly what your page requires of the Session object via the EnableSessionState @Page attribute. The options are True , False ,or ReadOnly : ❑ EnableSessionState="True" : The page requires read and write access to the Session .The Ses- sion with that Sessio nID will be locked during each request. ❑ EnableSessionState="False" : The page does not require access to the Session .Ifthecode uses the Session object anyway, an HttpException is thrown stopping page execution. ❑ EnableSessionState="ReadOnly" : The page requires read-only access to the Session. A reader lock is held on the Session for each request, but concurrent reads from other pages can occur. The order that locks are requested is essential. As soon as a writer lock is requested, even before a thread is granted access, all subsequent reader lock requests are blocked, regardless of whether a reader lock is currently held or not. While ASP.NET can obviously handle multiple requests, only one request at a time gets write access to a Session. By modifying the @Page direction in default.aspx and retrieve.aspx to reflect each page’s actual need, you affect performance when the site is under load. Add the EnableSessionState attribute to the pages, as shown in the following code: VB — Default.aspx < %@ Page Language="VB" EnableSessionState="True" AutoEventWireup="false" 1045 Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1046 Chapter 22: State Management CodeFile="Default.aspx.vb" Inherits="_Default" % > VB — Retrieve.aspx < %@ Page Language="VB" EnableSessionState="ReadOnly" AutoEventWireup="false" CodeFile="Retrieve.aspx.vb" Inherits="Retrieve" % > C# — Default.asp < %@ Page Language="C#" EnableSessionState="True" CodeFile="Default.aspx.cs" Inherits="_Default"% > C# — Retrieve.aspx < %@ Page Language="C#" EnableSessionState="ReadOnly" CodeFile="Retrieve.aspx.cs" Inherits="Retrieve" % > Under the covers, ASP.NET is using marker interfaces from the System.Web.SessionState namespace to keep track of each page’s needs. When the partial class for default.aspx is generated, it implements the IRequiresSessionState interface, whereas Retrieve.aspx implements IReadOnlySessionState . All HttpRequest s are handled by objects that implement IHttpHandler . Pages are handled by a Page- HandlerFactory . You can find more on HttpHandlers in Chapter 25. Internally, the SessionStateModule is executing code similar to the pseudocode that follows: If TypeOf HttpContext.Current.Handler Is IReadOnlySessionState Then Return SessionStateStore.GetItem(itemKey) Else ’If TypeOf HttpContext.Current.Handler Is IRequiresSessionState Return SessionStateStore.GetItemExclusive(itemKey) End If As the programmer, you know things about the intent o f your pages at compile time that ASP.NET can’t figure out at runtime. By including the EnableSessionState attribute in your pages, you allow ASP.NET to operate more efficiently. Remember, ASP.NET always makes the most conservative decision unless you give it more information to act upon. Performance Tip: If you’re coding a page that doesn’t require anything of the Session, by all means, set EnableSessionState="False" . This causes ASP.NET to schedule that page ahead of pages that require Session and helps with the overall scalability of your app. Additionally, if your application doesn’t use Session at all, set Mode="Off" in your web.config file to reduce overhead for the entire application. Out-of-Process Session State Out-of-process session state is held in a process called aspnet_state.exe that runs as a Windows Service. You can start the ASP.NET state service by using the Services MMC snap-in or by running the following net command from an administrative command line: net start aspnet_state 1046 Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1047 Chapter 22: State Management By default, the State Service listens on TCP port 42424, but this port can be changed at the registry key for the service, as shown in the following code. The State Service is not started by default. HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Services \ aspnet_state \ Parameters \ Port Change the web.config ’s settings from InProc to StateServer , as shown in the following code. Additionally, you must include the stateConnectionString attribute with the IP address and port on which the Session State Service is running. In a Web farm (a group of more than one Web server), you could run the State Service on any single server or on a separate machine entirely. In this example, the State Server is running on the local machine, so the IP address is the localhost IP 127.0.0.1. If you run the State Server on another machine, make sure the appropriate port is open — in this case, TCP port 42424. < configuration > < system.web > < sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424"/ > < /system.web > < /configuration > The State Service used is always the most recent one installed with ASP.NET. That means that if you are running ASP.NET 2.0/3.5 and 1.1 on the same machine, all the states stored in Session objects for any and all versions of ASP.NET are kept together in a single instance of the ASP.NET State Service. Because your application’s code runs in the ASP.NET Worker Process ( aspnet_wp.exe ,or w3wp.exe )and the State Service runs in the separate aspnet_state.exe process, objects stored in the Session can’t be stored as references. Your objects must physically leave the worker process via binary serialization. For a world-class, highly available, and scalable Web site, consider using a Session model other than InProc. Even if you can guarantee via your load-balancing appliance that your Sessions will be sticky, you still have application-recycling issues to contend with. The out-of-process state service’s data is persisted across application pool recycles but not computer reboots. However, if your state is stored on a different machine entirely, it will survive Web Server recycles and reboots. Only classes that have been marked with the [Serializable] attribute may be serialized. In the context of the Session object, think of the [Serializable] attribute as a permission slip for instances of your class to leave the worker process. Update the SmartSessionPage file in your \ App_Code directory to include a new class called Person ,as shown in Listing 22-5. Be sure to mark it as Serializable or you will see the error shown in Figure 22-4. As long as you’ve marked your objects as [Serializable] , they’ll be allowed out of the ASP.NET process. Notice that the objects in Listing 22-5 are marked [Serializable] . 1047 . are running ASP. NET 2.0 /3. 5 and 1.1 on the same machine, all the states stored in Session objects for any and all versions of ASP. NET are kept together in a single instance of the ASP. NET State. is held in a process called aspnet_state.exe that runs as a Windows Service. You can start the ASP. NET state service by using the Services MMC snap -in or by running the following net command from. are included in the Session. Here’s an interesting language note: In Listing 22 -3 the name of the private string value collides with the public property in VB because they differ only in case. In