Tuesday, 11 February 2014

ASP.NET State Management Techniques

What is the need of State Management?

In today's world everything is becoming Internet / Web based. Whatever work it may be, whether related to Banking, Education, Entertainment or Shopping we can do it over the internet very easily. If we try to compare today's generation with the previous generation then I would simply say that we cannot live without the Internet. 

As food, clothing and shelter are our basic needs, the Internet has also become a part of our basic necessities. If we think about our future, then I would say "Cloud Computing", which is already well developed and is currently still evolving, will take this Internet World to a higher level. 

ASP.NET-state-management1.gif

So now, enough of this lecture, and let's move on with the actual content.

Let us assume that someone is trying to access a banking website and he has to fill in a form. 
So the person fills in the form and submits it. After submission of the form, the person realizes he has made a mistake. So he goes back to the form page and he sees that the information he submitted is lost. So he again fills in the entire form and submits it again. This is quite annoying for any user. So to avoid such problems "STATE MANAGEMENT" acts as a savior for developers like us.

If you do not understand what the word "STATE" means, then think about it, as some interaction between the User and the Server.

Definition - State Management can be defined as the technique or the way by which we can maintain / store the state of the page or application until the User's Session ends.

ASP.NET-state-management2.gif

What is HTTP Protocol?

Hyper Text Transfer Protocol is the base or I would rather say, is the core part or the foundation of our World Wide Web. It basically makes use of the TCP Protocol for communicating with the server. A specific port is being used by this protocol while communicating with the server.

ASP.NET-http-protocol.gif

HTTP provides various methods which represent the actions it will perform while communicating with the server. Some of its methods are "GET", "POST", "DELETE", "HEAD", etc....

HTTP acts like a bridge between the client and the server.

If a user tries to access a website, say (http://www.asp.net/) and let us assume that the requested page is an ASPX page. So what happens is, the HTTP Protocol takes the request being sent by the client (using his browser) and sends it to the server. The server then locates the requested page and tells the ASP.NET engine to process the request. 

The ASP.NET Engine processes the request and sends a response back to the client in an HTML format. Once again the HTTP protocol is relevant; the HTTP protocol takes the response and sends it to the client, thus the response is shown in the client's browser.
So the HTTP Protocol, which is also called a "Request - Response" Protocol, acts like a bridge between the client and the server.

Some Technical Facts
It is very important to know what happens when the user sends a request to the server and what the server does to process that request. This is not related to state management, but it is the core part of any Request-Response Process. I will be explaining in brief about that process. The explanation includes information about: "What are all the objects created during the request?", "Who processes the Request?" etc...

Fact 1 - This is just a brief description of the ASP.NET environment creation:
  • Whenever the user sends a request to the server, the server first locates the page and checks who will process the request.
  • Then it creates an App Domain which is a space in the memory / Memory Unit which creates a Hosting Environment.
  • It then creates 4 different Objects like:
    • Http Response Object
    • Http Request Object
    • Http Context Object
    • Http Application Object
  • These objects then process the request and sends a response to the user in HTML format.
I have represented the entire flow in a diagram so that you will understand the concept in a much better way.

ASP.NET-Environment.gif
  • Note - The App Domain is created only once for each Website or an application. The Http Application Object is also created only once. The creation happens when the user requests a website for the first time.
  • To Learn more about the ASP.Net Environment Creation, please click on the following link, an article written by Shivprasad Koirala:
http://www.c-sharpcorner.com/uploadfile/shivprasadk/Asp-Net-application-and-page-life-cycle/

This is again a video link, which is beautifully presented by Shivprasad Sir on ASP.NET Environment Creatio:
http://www.youtube.com/watch?v=sOVBrLvhOHU

Fact 2 - HTTP Protocol is a Stateless protocol
  • As I mentioned above, State basically means an interaction between the user and the server and the HTTP Protocol acts like a bridge between them.
  • But since the HTTP Protocol is a Stateless protocol, it is not able to store the state during that session. With each request the objects are created and memory is being allocated. These resources are destroyed once the server completes its process.
  • So the cycle of "Creation Of Objects and Utilization of Server Memory" ---- "Processing Of the Request" ---- "Destruction of the Created Resources" continues for each and every user's request.
  • In such a case, the information given by the user or the State of an Application gets lost during each round trip. So this is where State Management helps.
I hope, by now you have understood the basic concept and the need of State Management. In the next part I will be explaining about the various techniques which ASP.NET provides to maintain the State of an Application. 

So come on, let's move onward.

State Management Techniques 

ASP.NET provides us with 2 ways to manage the state of an application. It is basically divided into the 2 categories:
  1. Client Side State Management.
  2. Server Side State Management.
Client Side State Management - It is a way in which the information which is being added by the user or the information about the interaction happened between the user and the server is stored on the client's machine or in the page itself. The server resources (e.g. server's memory) is not at all utilized during the process.

This management technique basically makes use of the following:
  1. View State
  2. Hidden Fields
  3. Query String
  4. Cookies
View State
View State can be used to maintain the State at a page level. The term "Page Level" means that the information is being stored for a specific page and until that specific page is active (i.e. the page which is being currently viewed by the user). Once the user is re-directed or goes to some other page, the information stored in the View State gets lost. 

It basically makes use of a "Dictionary Object" to store data, which means that the information is stored in a key and value pair. It stores this information in a Hidden field on the page itself in a hashed format. A View State can store a string value only of a specific length. If the length is exceeded then the excess information is stored in another hidden field.

Using a View State is quite simple. In fact, it is the default way for storing the page or the control's information. Typically the View State is used, when we want a user to be re-directed to the same page and the information being added by the user remains persistent until the user is on the same page.

Here I have shown how to use and assign a value to a View State and how to read a value from a View State.

Example - A user fills in a Registration Form.

Storing-Value-in-a-View-State.gif

Storing Value in a View State

Storing-Value-in-a-View-State1.gif

Information - Here in the code above, "Username" is the key and the value is the text being inputted by the user (txtFName.Text).

Retrieving Value from a View State

Retrieving-Value-from-a-View-State.gif

Information - Here as you can see in the image, I am retrieving the value from the View State, in the Page Load event of a page by first checking if the View State is not empty or null and then assigning its value to a Label. This is just a simple example which is shown to make you understand about, how to use a View State.

View State Information is stored in a Hashed Format

View-State-Information-is-stored-in-a-Hashed-Format.gif

Information - If you look at the page source, then this is the way View State stores the value.

View State Settings

View State is customizable. With the term "Customizable" I mean, that we can apply settings to a View State to store a value at various levels. We can set the View State at various levels like:
  1. Setting View State at Application Level - If we do not want our pages in the Application to use view state, then we can disable or enable it in the web.config file, as shown in the image below.
    View-State-Settings1.gif
  2. Setting View State at Page Level - If we do not want a specific page to use View State, then we can disable or enable it in the @ Page Directive which is the first line of our aspx page.
    View-State-Settings2.gif
  3. Setting View State at Control Level - If we do not want a specific control to use View State, then we can disable or enable it at a Control Level as follows:
    View-State-Settings3.gif
Advantages of using a View State
  1. It is very simple to use.
  2. Data is stored in hashed format and hence a layman won't be able to understand the value of the View State (It still can be hacked by Hackers, so to make it more secure we should try to store the value in an encrypted format.). Check out this link http://msdn.microsoft.com/en-us/library/ms178199(v=vs.85).aspx to understand, how to secure View State.
  3. It is customizable, as shown above.
Disadvantages of using a View State
  1. Information is not encrypted, so it can be easy for a Hacker to get its value.
  2. Cannot be used to store sensitive data (eg: Passwords, Credit Card Pins, etc).
  3. Might make a page heavy if lots of data is stored in View State.
Hidden Fields
ASP.NET provides a server control called "Hidden Field" which can be used to store a value at a page level, which is similar to a View State. The value of the Hidden Field is sent along in the HTTP Form Collection along with the value of other controls.
 
Example - Take the same example of the User filling an online registration form.

Setting Value to a Hidden Field

Setting-Value-to-a-Hidden-Field.gif

Information - Here I am setting a value to a Hidden field, as shown in the image above. I am taking input from the User (txtUsername.Text) and assigning it to the Hidden Field's Value property. It basically stores only 1 value in its property.

Retrieving Value from a Hidden Field

Retrieving-Value-from-a-Hidden-Field.gif

Information - As you can see, I am retrieving a value from the value in the Hidden field and assigning it to a label. The Hidden Field's "Value" property returns a string by default. If you want an integer value, then you will have to convert it explicitly.

A Hidden Field stores a value at a Page Level

If you look at the page source after assigning a value to a Hidden Field, then you will see that it stores the value on the page itself. Once the user is redirected to some other page, then the value is lost. 
Hidden-Field-stores-value-at-a-Page-Level.gif
I had passed "Shivanand" as a value from my textbox and assigned that value to a hidden field.

Advantages
  1. Very simple to use.
  2. Hidden Fields store the value in the page itself, hence do not use server resources.
Disadvantages
  1. Will make a page heavy, if too many Hidden Fields are used to store data.
  2. Cannot store sensitive data, as the value that is stored is neither hashed, nor encrypted.
Query String
A Query String is a string which is appended to the end of the Page URL. It is very simple to use and can be used to send data across pages. It stores information in a key - value pair. A "?" signature is used to append the key and value to the page URL.

Way to pass a value using Query String

pass-value-using-Query-String.gif

Information - This preceding code will send the Username to another page and use that value on that page. We should never send sensitive data using Query String, since the data that is being sent can easily be tampered with by anybody. If you still want to send information using Query String, then encrypt the data using any ASP.NET Encryption technique so the data cannot be tampered with.

Way to read Query String value
read-Query-String-value.gif
Information - To read the value of the query string, you should use the Request Object as shown in the image above. You can send multiple parameters in the query string along with its respective value. For sending multiple parameters, you can separate the parameters using the "&"delimeter.

Cookies

ASP.Net provides another way of state management, which is by using Cookies. Cookies are one of the best ways of storing information. It is nothing but a text file which is stored on the client's machine. 

When the user sends a request to the server, the server creates a cookie and attaches a header and sends it back to the user along with the response. The browser accepts the cookie and stores it at a specific location on the client's machine. Even large sites like Gmail, Facebook, Yahoo use cookies.
There are 2 ways of assigning / storing values in cookies.
  1. Using the Request ObjectUsing-the-Request-Object.gif
    Information - Here I have made use of the Request object. The Cookies property of the HTTPResponse Object and the HTTPRequest Object can be used to assign values to the Cookies Collection and get values back from the Collection. We can also store multiple values in a cookie.
     
  2. Using the HTTPCookies ObjectUsing-the-HTTPCookies-Object.gif
    Information - Another way of adding values to a cookie is using the "HTTPCookie" class. Its constructor takes either 1 or 2 parameters. If you want your cookie to expire after a specified time then you can even set the expiration date for that cookie. And the last line "Response.Cookies.Add(myCookie)" will add that cookie to the Cookies Collection.
Advantages
  1. Very easy to use.
  2. Stored on the client's machine, hence no server resources are utilized.
Disadvantages
  1. A user can disable cookies using browser settings.
  2. Since the cookies are stored on the client's machine, there are chances that if the client's machine is hacked then the hacker can view these cookie values. Hence we should not store sensitive information in cookies.
Server Side State Management - It is another way which ASP.NET provides to store the user's specific information or the state of the application on the server machine. It completely makes use of server resources (the server's memory) to store information. 

This management technique basically makes use of the following:
  1. Application State
  2. Session State
Application State:
  1. If the information that we want to be accessed or stored globally throughout the application, even if multiple users access the site or application at the same time, then we can use an Application Object for such purposes.
  2. It stores information as a Dictionary Collection in key - value pairs. This value is accessible across the pages of the application / website.
  3. There are 3 events of the Application which are as follows
    • Application_Start
    • Application_Error
    • Application_End
Example - Just for an example, I am setting the Page title in the Application Start event of the Global.asax file.

Code for setting value to the Application Object - "PageTitle" is the Key and "Welcome to State Management Application" is the value.
State-Management-Application.gif
Code for reading value from the Application Object 
State-Management-Application1.gif
Session State

Session is one of the most common way which is being used by developers to maintain the state of the application. The Session basically stores the values as a dictionary collection in key/value pairs. It completely utilizes server resources to store the data. It is a secure way of storing data, since the data will never be passed to the client.

For each and every user, a separate Session is created, and each and every Session has its Unique ID. This ID is being stored in the client's machine using cookies. If there are multiple users who are accessing a web application, then for each user a separate Session is created. If a single user logs in and logs out the Session is killed, then if the same user again logs into the application, then a new Session ID is being created for the same user.

The Session has a default timeout value (20 minutes). We can also set the timeout value for a session in the web.config file.
Session-State.gif
There are various ways in which we can store a session and they are as follows:
  1. OFF
  2. InProc
  3. State Server
  4. SQL Server
OFF - If we do not want to use sessions in our application, then we can set the mode as "OFF".

InProc - This is the default mode which is used in ASP.NET to store session variables. InProc mode basically stores the session value in the process in which the ASP.NET application is running. Since it is stored in the same process, it provides high performance as compared to other modes.

State Server - If we use this mode, then a separate Windows Service which runs in a different process stores the Session. It basically isolates the Session from the ASP.NET running process. It provides less performance as compared to the Inproc mode.

SQL Server - This mode stores the Session in SQL Server instead of the server's memory. If our application is stored on multiple server machines, then it becomes difficult to use the "Inproc" mode since the request can go to any server for processing. So if we want to use sessions in such cases, then we can store the Session in SQL Server so that it becomes centralized and can be accessed by any server during the request process. It is the most secure way of storing Sessions but gives the lowest performance for an application.

Code to write values into a Session
write-values-into-a-Session.gif
Code to read values from  Session
read-values-from-Session.gif

ASP.NET Performance Techniques

Caching ASP.NET Applications

This section discusses how caching works in ASP.NET applications. There are primarily three forms of caching. These are page caching, page fragment caching, and data caching.

Page Caching

Page caching is the process of persisting an entire page on the server, proxy server, or the client browser so that the next time it is retrieved, it does not have to be generated by ASP.NET.

Using the @ OutputCache Directive

Page caching is enabled by including the directive in bold (line 2) in the .aspx file shown in Listing 33.1.

Listing 33.1  .aspx File Containing Page Caching Directive

1:   <%@ Page language="c#" Debug="true" Codebehind="WebForm1.pas" AutoEventWireup="false" Inherits="WebForm1.TWebForm1" %>
2:   <%@ OutputCache Duration="20" Location="Any" VaryByParam="none" %>
3:   <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
4:   
5:   <html>
6:     <head>
7:       <title></title>
8:       <meta name="GENERATOR" content="Borland Package Library 7.1">
9:     </head>
10:  
11:    <body ms_positioning="GridLayout">
12:    <form runat="server">
13:      <asp:label id=Label1
14:                 style="Z-INDEX: 1; LEFT: 62px; POSITION: absolute;
15:                 TOP: 14px" runat="server">Label</asp:label>
16:      <asp:button id=Button1
17:                  style="Z-INDEX: 2; LEFT: 70px; POSITION: absolute;
18:                  TOP: 46px" runat="server" text="Button">
19:      </asp:button>
20:    </form>
21:  </body>
22:  </html>

Find the code on the CD: CodeChapter 33Ex01.

In this example, the @ OutputCache directive includes three attributes. The first, Duration, specifies how long (in seconds) the cache will retain the page before regenerating its HTML. The second attribute, Location, determines where the cache is stored. The third allows you to create a different version of the resulting page based on values provided in the comma-separated list following the VaryByParam attribute. These and other attributes are more fully explained in Table 33.1. Some of these attributes pertain to page or control caching or both.

Table 33.1  @ OutputCache Attributes

AttributeDescription
DurationSpecifies the time in seconds that the page is cached. By specifying a value, an expiration policy is established for the page or control being cached. This is a required attribute.
LocationLocation allows you to specify where the page is cached. It can be one of the following values.
Any—The item can be cached on any of the following locations. This is the default setting.
Client—The item is cached in the client's browser.
Downstream—The item is cached on a downstream server.
None—There is not page caching performed.
Server—The item is cached on the server.
SharedShares deals with user controls and determines whether the control's cache can be shared with other pages.
VaryByCustomSemicolon separated strings that allow varying pages based on browser type or custom strings.
VaryByHeaderSemicolon separated list of headers that can be used for serving different pages based on header information.
VaryByParamSemicolon separated list of strings representing parameters that are used in determining varying page output. These strings correspond to attributes sent with aGET method or parameters sent with the POST method. This attribute is required and might contain an empty string.
VaryByControlSemicolon separated list of user-control property names. This is only valid for control caching (fragment caching).

The example in Listing 33.1 illustrates how page caching works. It is a page that contains a Button and a Label control. The code-behind for the Button's Click event performs the following:
procedure TWebForm1.Button1_Click(sender: System.Object;
  e: System.EventArgs);
begin
  Label1.Text := System.String.Format('Time on the Server is: {0}',
    [System.DateTime.Now.ToLongTimeString]);
end;
When running the application, clicking the button will reveal that the page is being cached. The time that is written to the page does not change until the cache has expired, as determined by the Duration attribute of the @ OutputCache directive.

Varying by Parameters

I will illustrate one of the varying attributes, specifically the VaryByParam attribute. This attribute can have one of three possible values, including none, an asterisk *, and a valid string that represents a GET method attribute or aPOST parameter name. VaryByParam results in a different page being cached for each distinct request (as determined by the parameters being passed). When using the * as shown next, all parameters are taken into account.
<%@ OutputCache Duration="20" Location="Any" VaryByParam="*" %>
You can also spell out a specific parameter by name, causing only the specified parameters to be taken into account by distinguishing a separate request needing to be cached. This is illustrated here:
<%@ OutputCache Duration="20" Location="Any" VaryByParam="FirstName" %>
To illustrate this, Listing 33.2 is the .aspx file for an example similar to that shown in Listing 33.1. Notice that the VaryByParam attribute now contains an asterisk.

Listing 33.2  Varying by Parameter Example

1:   <%@ Page language="c#" Debug="true" Codebehind="WebForm1.pas" AutoEventWireup="false" Inherits="WebForm1.TWebForm1" %>
2:   <%@ OutputCache Duration="120" Location="Any" VaryByParam="*" %>
3:   <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
4:   
5:   <html>
6:     <head>
7:       <title></title>
8:       <meta name="GENERATOR" content="Borland Package Library 7.1">
9:     </head>
10:  
11:    <body ms_positioning="GridLayout">
12:    <form runat="server">
13:      <asp:label id=Label1
14:                 style="Z-INDEX: 1; LEFT: 38px; POSITION: absolute;
15:                 TOP: 14px" runat="server">Label</asp:label>
16:    </form>
17:  </body>
18:  </html>

Find the code on the CD: CodeChapter 33Ex02.

When entering a URL such as
http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Bob
the output written will be
"Bob, the time on the Server is: 8:46:04 a.m."
Changing the URL to
http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Sam
results in the output
"Sam, the time on the Server is: 8:46:17 a.m."
Assuming that VaryByParam="FirstName" and using Bob as the parameter, you will see that the original output is returned with the time appearing to move backward at 8:46:04 a.m. What's happening here is that there are two versions of this page being cached—one for when the FirstName parameter equals Bob, and the other for when FirstName is Sam. As another interesting point, consider the following two URLs:
http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Bob&LastName=Jones
http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Bob&LastName=Archer
Both would result in the same cached page being returned. However, changing the @ OutputCache directive to the following would have different results:
<%@ OutputCache Duration="120" Location="Any" VaryByParam="FirstName" %>
With this directive, only requests in which the FirstName parameter is different will result in different cached pages. The previous two URLs containing the same FirstName but different LastName parameters will be served the same cached page.

Varying by Headers

Incertain situations, you'll want your ASP.NET applications to take advantage of browser capabilities. However, you won't want to serve pages to browsers that use capabilities not supported by the target browser. Therefore, when using caching, it doesn't make any sense to be caching a page that won't be supported by the client's browser. The following @ OutputCache directive shows how you can create a different page depending on HTTP header information—specifically the User-Agent header by including the VaryByHeader attribute.
<%@ OutputCache Duration="120" Location="Any" VaryByParam="*" VaryByHeader="User-Agent" %>
The client's browser can be identified by the User-Agent HTTP header. You can specify other HTTP headers in the VaryByHeader or multiple headers separated by semicolons.

Varying by Custom Strings

You can get very specific about the requirements that determine cached page variations by using the VaryByCustom attribute of the @ OutputCache directive.
There are two ways to use this attribute. The first and simplest is to specify a value of "Browser" as shown here:
<%@ OutputCache Duration="120" Location="Any" VaryByParam="*" VaryByCustom="Browser" %>
This causes behavior similar to the VaryByHeader example previously explained. It differs in that VaryByCustom="Browser" only uses the browser type and major version rather than the additional information that might be included in the User-Agent header.
Another way to use the VaryByCustom attribute is to specify a user-defined string. In doing so, you must override the GetVaryByCustomString() method of the HttpApplication class in Globals.pas. This would look like the following code:
function TGlobal.GetVaryByCustomString(Context: HTTPContext;
  custom: &String): System.String;
begin
   if custom = 'Country' then
     Result := GetCountry(Context)
   else 
     Result := GetVaryByCustomString(Context, custom);
end;
This illustrates a way that you might cache pages based on the country of the user originating the request. This assumes that the GetCountry() method returns a string that would be used as the string by which to vary cached pages.

Page Fragment Caching

Page fragment caching is similar to page caching but instead of caching the entire page, you are caching specific elements of the page. This can be accomplished by caching user controls, which are covered in Chapter 34, "Developing Custom ASP.NET Server Controls." User controls can be used with the @ OutputCache directive just like pages can. Some attributes aren't supported because they make no sense since user controls exist in the context of the page. These are Location and VaryByHeader. The following .ascx file defines a user control that uses such caching:
<%@ Control Language="c#" AutoEventWireup="false" Codebehind="WebUserControl1.pas" Inherits="WebUserControl1.TWebUserControl1"%>
<%@ OutputCache Duration="20" VaryByParam="*" %>
<asp:label id=Label1
   style="Z-INDEX: 101; LEFT: 38px; POSITION: absolute; TOP: 38px"
   runat="server">Label</asp:label>

Find the code on the CD: CodeChapter 33Ex03.

This control simply displays the system time. To illustrate the partial page caching, it is included on a page that also displays the system time but is not cached. Figure 33.1 shows the output after refreshing the browser several times. You can see that that user control retains its original time and will do so until the cache has expired.

Figure 33.1
Caching a user control.

Data Caching

Data caching is a way to cache data of any type. This is particularly useful for obtaining a performance boost by not having to request data from a data source. Instead, this can be done once to fill a DataSet that you then cache. Subsequent requests for this DataSet will then retrieve it from the cache. To illustrate this, we'll need to examine the Cache class.

The Cache Class

The Cache class is defined in the System.Web.Caching namespace and provides the capability to store information to memory at the application level, which can be retrieved upon different requests. This works similarly to the Application class, which I discuss later in this chapter.
There are two properties and four methods of interest regarding the Cache class that are described in Table 33.2.

Table 33.2  Cache Class Properties and Methods

Property/MethodDescription
CountThis property returns the number of items that are currently stored in the Cache.
ItemThis property is an indexer array that returns the item by a specified key.
Add()This method allows you to add an item to the Cache. You can specify as parameters dependencies, an expiration policy, a priority policy, and a remove callback method. Add() fails if an item already exists in the Cache for a given key.
Get()This method returns an item from the Cache by the specified key.
Insert()This method inserts an item into the Cache, replacing any item that exists with the same key. You can include the same parameters as with the Add() method.
Remove()This method removes an item from the Cache with the specified key.

As indicated for the Cache.Add() and Cache.Insert() methods, there are several parameters that pertain to the item being placed in the Cache. These are discussed in Table 33.3 in the order that they appear as parameters.

Table 33.3  Cache Class Properties and Methods

Property/MethodDescription
KeyThe string key used to refer to the cached item.
ValueThe item (a System.Object parameter) that is added to the Cache.
DependenciesA single or multiple files, directories, or the keys to another cached item on which this new item depends. When either the file or cached item changes, this cached item is removed from the Cache.
AbsoluteExpirationThe time at which the item is removed from the Cache.
SlidingExperationThe time interval at which the item is removed from the Cache if it has not been accessed during this time. If the item is accessed, the expiration is set to be the access time plus the time specified by this value.
PriorityCacheItemPriority enumeration value that is used by the Cache when evicting objects.
OnRemoveCallbackA delegate (event handler) that gets invoked whenever a cached item is removed.

The following code illustrates the use of the Cache class:
  if Cache['DateToday'] <> nil then
    Response.Write('From Cache: '+
      DateTime(Cache['DateToday']).Today.ToLongDateString)
  else begin
    Response.Write('From System: '+
      System.DateTime.Today.ToLongDateString);
    Cache.Add('DateToday', System.DateTime.Today, nil, GetMidnight,
      Cache.NoSlidingExpiration, CacheItemPriority.Default, nil)
  end;
This code displays today's date retrieved from the system or the Cache if today's date exists in the Cache. The call to Cache.Add() illustrates using the AbsoluteExpiration parameter, which is set to midnight through the helper function GetMidnight(). In case you're wondering, the GetMidnight() function simply returns a DateTime value for tomorrow by adding 1 day to today's date:
function GetMidnight: System.DateTime;
begin
  Result := System.DateTime.Today.AddDays(1);
end;

Data Caching Example

Caching simple data types can be useful. The real value in performance is gained when you Cache data that would otherwise be retrieved from another resource such as a database. Listing 33.3 shows an excerpt from an example that illustrates this technique.

Listing 33.3  Caching Data

1:   const
2:     c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3:     c_sel   = 'select * from products';
4:   
5:   procedure TWebForm1.Page_Load(sender: System.Object;
6:     e: System.EventArgs);
7:   begin
8:     if not IsPostBack then
9:       GetData;
10:  end;
11:  
12:  procedure TWebForm1.GetData;
13:  var
14:    sqlcn: SqlConnection;
15:    sqlDa: SqlDataAdapter;
16:    Ds: DataSet;
17:    dtView: DataView;
18:  begin
19:  
20:    dtView := Cache['dvProducts'] as DataView;
21:    if dtView = nil then
22:    begin
23:      sqlcn := SqlConnection.Create(c_cnstr);
24:      sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
25:      Ds := DataSet.Create;
26:      sqlDA.Fill(Ds);
27:      try
28:        dtView := DataView.Create(Ds.Tables['Table']);
29:        Cache['dvProducts'] := dtView;
30:        Label1.Text := 'From Database';
31:      finally
32:        sqlcn.Close;
33:      end;
34:    end
35:    else
36:      Label1.Text := 'From Cache';
37:  
38:    DataGrid1.DataSource := dtView;
39:    DataBind;
40:  end;
41:  
42:  procedure TWebForm1.DataGrid1_PageIndexChanged(source: System.Object;
43:    e: System.Web.UI.WebControls.DataGridPageChangedEventArgs);
44:  begin
45:     GetData;
46:     DataGrid1.CurrentPageIndex := e.NewPageIndex;
47:     DataGrid1.DataBind;
48:  end;

Find the code on the CD: CodeChapter 33Ex04.

The GetData() procedure (lines 12–40) is the one we'll want to examine closely. Line 20 attempts to retrieve a DataView from the Cache. If it does not exist, it will be nil. That being the case, the data is obtained from the Northwind database. In addition to extracting this data from the database, it gets added to the Cache (line 29). Upon subsequent requests for this page, line 20 should not return nil but instead the cached DataView.
This technique works well for data that will not change, such as lookup information. It will also work for data that does change—in which case, you will need to develop code to synchronize data stored in the Cache and the database. This could be as simple as refreshing the entire DataView when a change is made. It can also increase in complexity, such as updating both the Cache and database with only the modified data. Another useful solution is to cache the information until some specific time (for instance, midnight). This works nicely for rarely updated data. Several factors will determine approaches you should take, such as scalability and system requirements to name a few. For instance, you wouldn't want to expend system memory by caching huge DataSets, which would defeat any performance benefits you intended to gain.

Cache File Dependencies

It is possible to make a cached item dependent on single or multiple associated files, directories, or other cached items. When the associated entity is modified, the dependant item is removed from the Cache. Listing 33.4 shows how to establish such a dependency using the Cache.Insert() method.

Listing 33.4  Establishing a Dependency on a Cached File

1:   procedure TWebForm1.Button1_Click(sender: System.Object;
2:     e: System.EventArgs);
3:   var
4:     str: String;
5:   begin
6:     str := Cache['MyData'] as System.String;
7:     if str = nil then
8:     begin
9:       Label1.Text := 'Not in Cache';
10:      Str := 'Now in Cache';
11:      Cache.Insert('MyData', Str,
12:        CacheDependency.Create(MapPath('cache.txt')));
13:    end
14:    else
15:      Label1.Text := Str;
16:  end;

Find the code on the CD: CodeChapter 33Ex05.

Lines 11–12 associate the file cache.txt as the dependency for the cached 'MyData' item. When cache.txt is modified, 'MyItem' will be removed from the Cache. This allows you to establish an external mechanism by which you can invoke a refresh of the cached information. The following section illustrates a practical example of this technique.

Tip - Use the MapPath() method to translate virtual paths to physical paths as used in Listing 33.4.

In this example, when the file, 'cache.txt' is modified or deleted, the item keyed by 'MyData' is also removed from the Cache.
You can also establish a key dependency. A key dependency is one in which a cached item is dependent on another cached item. This is done by using the INSERT statement as
keyAry[0] := 'Item1';
keyAry[1] := 'Item2';
Cache.Insert('MyData', Str, CacheDependency.Create(nil, keyAry));
In this example, the item with the key 'MyData' becomes dependant on the items keyed as 'Item1' and 'Item2'.
The technique of creating an array of items can also be used in establishing a dependency on multiple files or directories by creating a string array of file or directory names.

Extending File Dependencies for Use in SQL Server

This section illustrates a realistic example of using cache dependencies.
Listing 33.5 shows a GetData() method similar to that seen in Listing 33.3.

Listing 33.5  GetData() with a Cached Dependency

1:   procedure TWebForm1.GetData;
2:   var
3:     sqlcn: SqlConnection;
4:     sqlDa: SqlDataAdapter;
5:     Ds: DataSet;
6:     dtView: DataView;
7:   begin
8:     dtView := Cache['dvEmp'] as DataView;
9:     if dtView = nil then
10:    begin
11:      sqlcn := SqlConnection.Create(c_cnstr);
12:      sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
13:      Ds := DataSet.Create;
14:      sqlDA.Fill(Ds);
15:      try
16:        dtView := DataView.Create(Ds.Tables['Table']);
17:        Cache.Insert('dvEmp', dtView,
18:          CacheDependency.Create(MapPath('cache.txt')));
19:        Label1.Text := 'From Database';
20:      finally
21:        sqlcn.Close;
22:      end;
23:    end
24:    else
25:      Label1.Text := 'From Cache';
26:    DataGrid1.DataSource := dtView;
27:    DataBind;
28:  end;

Find the code on the CD: CodeChapter 33Ex06.

Lines 17 and 18 are where the dependency is established. When the file 'cache.txt' is modified or deleted, the DataView is removed from the Cache. You would want to do this when the data in the database is modified, making the cached information out of sync. The question this raises is how to modify cache.txt. If the underlying database is modified through an external program, this external program can modify the file. If the database is modified by this same ASP.NET application, it too can modify the file; however, one must wonder why we wouldn't just refresh the data without dealing with the file at all.
The idea here is to invoke a refresh of the cached data whenever the data stored in the table that the DataView represents gets changed. It really doesn't matter where the data was modified. The way to do this is to create a trigger on the SQL table that modified the file. An example is shown here:
CREATE TRIGGER EMP_UPD_CACHE
ON Employees
FOR UPDATE, DELETE, INSERT
AS
  DECLARE @ShCmd VARCHAR(100)
  SELECT @ShCmd = 'echo '+ Cast(GetDate() as VARCHAR(25))+' > "C:Datacache.txt"'
  EXEC master..xp_cmdshell @ShCmd, no_output
This trigger will write information to the cache.txt file when a record is updated deleted or added to the Employees table, which will effectively invoke the refresh we desire.

Note - The preceding example assumes that the SQL Server runs on the same machine as the Web Server, or at least that the two servers see the same NTFS share.

Cache-Callback Methods

This section illustrates how you can associate a callback method with an item added to the Cache. This callback method gets invoked whenever the items with which it is associated gets removed from the Cache. The callback method takes the three parameters listed here:
  • key—String index for the cached item.
  • value—Value of the item removed from the Cache.
  • reason—Reason item was removed from the Cache. This value is one of the CacheItemRemovedReason enumeration values.
Valid values for the reason parameter are
  • DependencyChanged—A dependency on the item was modified.
  • Expired—The item reached its expiration period.
  • Removed—The item was removed from the Cache through the Remove() or Insert() method.
  • Underused—The item was removed by the system to free up memory.
Listing 33.6 demonstrates how to use the callback method with cached items.

Listing 33.6  Cache-callback Example

1:   procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2:   var
3:     keyAry: array[0..0] of String;
4:   begin
5:     if not IsPostBack then
6:     begin
7:       Cache.Insert('Item1', 'Item 1', nil,
8:         System.DateTime.Now.AddSeconds(10), Cache.NoSlidingExpiration,
9:         CacheItemPriority.Default, CacheItemRemoved);
10:      Cache.Insert('Item2', 'Item 2', nil,
11:        System.DateTime.Now.AddSeconds(10), Cache.NoSlidingExpiration,
12:        CacheItemPriority.Default, CacheItemRemoved);
13:      keyAry[0] := 'Item2';
14:      Cache.Insert('Item3', 'Item 3', CacheDependency.Create(nil, keyAry),
15:        System.DateTime.Now.AddSeconds(10), Cache.NoSlidingExpiration,
16:        CacheItemPriority.Default, CacheItemRemoved);
17:    end;
18:  end;
19:  
20:  procedure TWebForm1.CacheItemRemoved(Key: System.String; Value: TObject;
21:    Reason: CacheItemRemovedReason);
22:  begin
23:    FItemArray := Application['Log'] as ArrayList;
24:    if FItemArray = nil then
25:    begin
26:      FItemArray := ArrayList.Create;
27:      Application['Log'] := FItemArray;
28:    end;
29:    FItemArray.Add(Key+': '+Enum(Reason).ToString());
30:  end;
31:  
32:  procedure TWebForm1.btnRemove_Click(sender: System.Object;
33:    e: System.EventArgs);
34:  begin
35:    Cache.Remove('Item2');
36:    btnGetLog_Click(nil, nil);
37:  end;
38:  
39:  procedure TWebForm1.btnGetLog_Click(sender: System.Object;
40:    e: System.EventArgs);
41:  begin
42:    if Application['Log'] <> nil then
43:    begin
44:      DataGrid1.DataSource := Application['Log'];
45:      DataGrid1.DataBind;
46:      DataBind;
47:    end;
48:    Label1.Text := 'Items cached: '+Cache.Count.ToString
49:  end;

Find the code on the CD: CodeChapter 33Ex07.

In this example, lines 7–16 inserts three items to the Cache. The item inserted at line 14 (Item3), is also associated with Item2 through a dependency. This means that when Item2 is changed or removed, Item3 will be removed from the Cache.
Lines 20–30 show the callback method that is used when items are removed from the Cache. This method adds the item name and reason to an ArrayList that is stored in the HttpApplicationState object. This list is used by the btnGetLog_Click() event handler, which binds the ArrayList to a DataGrid. The btnRemove_Click() event handler removes Item2 from the Cache and calls the btnGetLog_Click() method to display the results. Because Item3 is dependent on Item2, removing Item2 should also result in Item3 being removed. This is verified in Figure 33.2.

Figure 33.2  

Results of the CacheItemRemoveCallback.

State Management in ASP.NET Applications

State management is related to caching—whereas caching keeps global state for multiple clients, session state keeps state for a single client. Web applications are stateless; therefore, they don't inherently track user information between requests. Each request is viewed as a distinct request entirely unrelated to previous requests.
ASP.NET provides several levels at which state can be managed. These are cookies, ViewState, Session, and Application.
The following sections cover each of these different state management mechanisms.

Managing State with Cookies

Cookies are basically text that the Web server can place in the client's browser. They are transferred via HTTP headers. As the user hits various pages within a Web site or application, the server can examine the content of these cookies. A cookie is associated with the domain of the server that initiated its creation. Therefore, a cookie can never be transferred to other domains. A cookie can be temporary in that it lasts for the current session only. It can also be persistent across multiple sessions. This is one mechanism that the server can use to maintain state information.

Creating a Cookie

Creating a cookie is simple. The cookie itself is encapsulated by the HTTPCookie class defined in the System.Web.HttpCookie namespace. The following code shows how to create a cookie whose value contains the text entered from a TextBox control:
var
  MyCookie: HttpCookie;
begin
  MyCookie := HttpCookie.Create('MyName', TextBox1.Text);
  Response.Cookies.Add(MyCookie);
  Response.Redirect('WebForm2.aspx')
end;

Find the code on the CD: CodeChapter 33Ex08.

This code creates a cookie with the name 'MyName' and adds it to the collection of cookies in the HttpResponse object. This is the server's way of telling the browser to maintain the cookie specified. To illustrate how the cookie is available in a separate request, the user is redirected to another page that will retrieve the cookie.

Retrieving Cookie Values

When the browser makes a request to a Web server, it sends along its collection of cookies for that domain. They can be retrieved through the HttpRequest.Cookies collection as shown here:
procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);
begin
  if Request.Cookies['MyName'] <> nil then
    Label1.Text := System.String.Format('Hello {0}, Welcome to the site',
      Request.Cookies['MyName'].Value)
  else
    Label1.Text := 'I don''t know you';
end;

Find the code on the CD: CodeChapter 33Ex08.

This code demonstrates how the cookie has been transferred by the browser as part of the request. The server can then obtain the cookie's value and, in this example, use it as part of a string displayed to the user.

Creating Persistent Cookies

A persistent cookie is one that the browser places on the user's hard drive in a directory that the browser knows about. The server can initiate this by adding an expiration date to the HTTPCookie.Expires property. The following code illustrates this procedure:
var
  MyCookie: HttpCookie;
begin
  MyCookie := HttpCookie.Create('MyName', TextBox1.Text);
  if cbxRemember.Checked then
    MyCookie.Expires := System.DateTime.Today.AddDays(30);
  Response.Cookies.Add(MyCookie);
  Response.Redirect('WebForm2.aspx')
end;
In this example, a CheckBox on the Web From determines whether the server will tell the browser to create a persistent cookie. If it does, the cookie is set to expire 30 days from today.
Now suppose that a user were to revisit a site with this code after having added a persistent cookie. The code in Listing 33.7 demonstrates how to use the cookie value to present a welcome message and to pre-populate TextBoxso the user wouldn't have to reenter her name.

Listing 33.7  Using a Cookie to Pre-populate Controls

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
begin
  if not IsPostBack then
    if not Request.Browser.Cookies then
      lblNoCookie.Text := 'Your browser does not support cookies.'
    else begin
      if Request.Cookies['MyName'] <> nil then
      begin
        lblWelcome.Text := System.String.Format('Welcome back {0}',
          Request.Cookies['MyName'].Value);
        TextBox1.Text := Request.Cookies['MyName'].Value;
      end;
    end;
end;

Find the code on the CD: CodeChapter 33Ex08.

To delete a cookie from the user's machine, you can do one of two things:
  • You can add another cookie of the same name that is session based; it has no assignment to the Expires property.
  • You can add a cookie with System.DateTime.Now assigned to the Expires property, causing the cookie to be removed from the user's machine.

Cookie Drawbacks

Although convenient and easily implemented, cookies do have their drawbacks. They can only contain a small amount of data, 4KB specifically. Additionally, they can only store string data. Therefore, you would have to convert data such as dates, integers, floats, and so on to strings prior to storing them in cookies. Cookies are also browser dependant, and some browsers do not support them. Last, they require that the user permits your application to store files on her machine. Cookies have gotten a bad rep because they consume space on users' machines without them knowing about it. Therefore, many users opt for disabling cookie support in their browsers.
Cookies are great to use for some circumstances. However, when it comes to managing state in more complex scenarios, ASP.NET has other means for doing this, which I discuss next.

Working with ViewState

ViewState is the component of ASP.NET pages that keeps track of information such as server control properties. It is handled automatically; you typically do nothing with it other than to disable it if you don't need it. It merits discussion here because there are things you can do to improve performance of your Web applications by making adjustments to ViewState. Plus, you can use that state bag to store custom information for these round-trips.
Here's how ViewState works. When you fill out a Web form, the data entered into the form is sent to the server as part of the POST/GET commands. The server, in turn, packages this information into a hidden field, ViewState, and sends them back to the client along with the rest of the response. The client doesn't do anything with this information. ViewState is supplied to the client so that it is sent back to the server to use upon subsequent requests. This is referred to as round-tripping. The simple case is that the server uses the information to set controls properties upon a post-back of the page.

Note - The actual name of the ViewState hidden field is __ViewState. I refer to it as simply ViewState in this section.

In many examples that I have seen to demonstrate ViewState, you're asked to create an ASP.NET application with a few form fields and a Submit button. You're told to fill out the fields and click the Submit button. The page will post-back, and the fields will be populated with the information you originally entered. If you click the Back button on your browser, the fields will retain their values. This is credited to ViewState.
It is true that the properties of controls, such as the Text property of the TextBox control, are added to the ViewState. However, control properties that are sent to the server as part of the POST command are used to generate the HTML in the response instead of those values contained in the ViewState. You can see this by disabling ViewState for the entire page of an application similar to that just described. You will see that the controls retain their values. In other words, the page is still stateful, even without ViewState.
So why bother with ViewState at all? Remember that the server uses values passed as part of the POST command to initialize controls with data in generating the HTML. The only properties that get initialized as such are those that were included in the POST command. Other properties rely on ViewState. For example, consider the page shown in Figure 33.3.

Figure 33.3  

Example customer lookup form.

Find the code on the CD: CodeChapter 33Ex09.

Imagine that this is a customer lookup form. The Lookup Customer button is disabled by default. The Page_Load() event handler contains the following code:
if not IsPostBack then
  btnLookup.Enabled := LoggedIn;
LoggedIn() simply returns True. It simulates some form of test for user authentication.
With ViewState enabled for the page, the page works as you would expect. When the user first goes to the page, btnLookup.Enabled is set to True. When clicking the Lookup Customer button, it retains its enabled state. However, when disabling ViewState for the page, clicking the Lookup Customer button reveals that it will be disabled on the post-back. This is because the LoggedIn() method is not invoked since this is a post-back. The server generates the HTML for the button based on its design-time property values. The server has no information in the POST command properties, nor is there any state information in ViewState from which it can determine a different property value for the button's Enabled property.
You can see where ViewState serves a good purpose. However, more often than not, you really do not need ViewState. It is generally recommended that you disable ViewState on all pages that do not need it. This will prevent performance loss resulting in the extra bytes being tagged along in your HTML documents.

Disabling ViewState on the Page

You can disable ViewState for an entire page by adding it to the @ Page directive. This is shown here:
<%@ Page language="c#" EnableViewState="False" Codebehind="WebForm1.pas" AutoEventWireup="True" Inherits="WebForm1.TWebForm1" %>

Disabling ViewState for a Control

You can disable ViewState for a specific control by simply setting its EnableViewState property to False in the Object Inspector. You can also edit the .aspx file directly as shown here:
<asp:button id=btnLookup
    style="Z-INDEX: 9; LEFT: 222px; POSITION: absolute;
    TOP: 206px" runat="server" enableviewstate="False"
    enabled="False" text="Lookup Customer">
</asp:button>

Adding Values to the State Bag

If you recall from the section on cookies, some browsers do not support cookies or the feature has been disabled by the user. You can store the same type of information in the ViewState by adding them to the state bag (orViewState property of each control). Adding a value to the state bag is simple, as shown in the following line:
ViewState.Add('MyData', 'MyDataText');
This adds the item 'MyDataText' keyed off the string 'MyData'. To reference an item in the state bag, simply index it by its string key:
Response.Write(ViewState['MyData']);

Find the code on the CD: CodeChapter 33Ex10.


Note - Unlike cookies, which are restricted to storing only strings, ViewState supports storing arbitrary objects.

Session State Management

Session state management occurs during the course of a user's visit to a site. It typically begins once the user visits the site and ends when the user leaves the site.
When a user first enters a site, ASP.NET creates a unique session for that user. This session is an instance of the HttpSessionState class. The session consumes a certain amount of memory for this user. Additionally, the user is given a unique ID, which is passed to the user's browser and returned to the server on each request during the run of the session. By default, this is all done via a cookie.
The session remains in memory until the user leaves the site or until the session has timed out, which, by default, occurs after 20 minutes of inactivity.
You can store information in the HttpSessionState class instance that can be retrieved upon subsequent requests.
Session information is maintained by a session state provider. This provider is run in one of three modes: InProcStateService, or SQLServer.
  • InProc—Session data is maintained within the same domain as the ASP.NET application. It is within the context of aspnet_wp.exe. This is the default setting.
  • StateServer—Session data is maintained within the context of a Windows NT Service aspnet_state.exe. This service can be run on the same or on a different machine.
  • SQLServer—Session data is maintained in a predefined SQL Server database.
  • Off—Session state is disabled.

Storing and Retrieving Information Using the Session Object

The following code demonstrates how to add data to the Session object:
Session.Add('UserName', TextBox1.Text);
To retrieve this same information, you would issue a statement such as
Response.Write('Welcome '+Session['UserName'].ToString);
You can add any object to the Session class. For instance, the following code adds a DataSet to the Session class:
Session.Add('MyData', DataSet1);
To remove an item from the Session object, simply call its Remove() method as
Session.Remove('MyData');

Changing the Default Session Timeout

You can change the session's default timeout by modifying the web.config file. The default timeout is 20 minutes. The following modification to web.config sets the timeout to 60 minutes:
<configuration>
  <system.web>
    <sessionState timeout="60"/>
  </system.web>
</configuration>

Making Sessions Cookieless

Earlier, I stated how the SessionID is passed between the server and browser via a cookie. When discussing cookies in this chapter, I pointed out some drawbacks to cookies, such as the user disabling cookie support in her browser. This would render the Session cookie unusable for an ASP.NET site. Therefore, you can change how ASP.NET transfers the SessionID. It entails modifying the web.config file as shown here:
<configuration>
  <system.web>
    <sessionState cookieless="true" />
  </system.web>
</configuration>
When this is done, the SessionID is passed as part of the URL. This is called cookie munging. A URL with the SessionID would look something like the one here:
http://www.xapware.com/SessionEx/(kxn1f555r4xgbe45jlr4wcyf)/WebForm1.aspx
The SessionID is the portion in bold.
Using this technique has a few drawbacks. First, you cannot place absolute links to pages within your site. All links must be relative to the current page. If you can live with that, this is a great way to get around cookie limitations on the client side. Second, it reduces the usefulness of client and proxy-side caching of complete HTML pages. URLs change for each session, so yesterday's cached pages will not be used today, for instance.

Storing Session Data in a Session State Server

By default, the ASP.NET applications use an in-process Session State Server. This ties session information directly to the ASP.NET application in that they are both running in the same process. If the ASP.NET application were to be shut down, all session information would be lost. This is the disadvantage of the InProc mode. The advantage is one of performance. With the session information existing within the same process and machine for that matter, data retrieval is faster. The following web.config setting shows the Session's default InProc setting:
<configuration>
  <system.web>
    <sessionState  mode="InProc"/>
  </system.web>
</configuration>
To configure for an Out-of-Proc Session State Server, the web.config file would contain something similar to
<configuration>
  <system.web>
    <sessionState mode="StateServer" 
      stateConnectionString="tcpip=192.168.0.20:42424"/>
  </system.web>
</configuration>
tcpip refers to the IP address of the machine hosting the session state server. In this example, the port used is 42424. You can change this and be sure to make it a port unused by other processes on the machine.
To start the session state server on the machine that will be hosting it, you simply have to issue net start aspnet_state on the command line as shown here:
C:>net start aspnet_state
The ASP.NET State Service service is starting.
The ASP.NET State Service service was started successfully.
C:>
By storing session information out of process, you gain the benefit of reducing the chances of session data being lost. If the ASP.NET application or if the Web server were to be shut down, the session information would be retained by the session state server on another machine most likely. Again, the performance here is reduced and not only because of network transfer of data, but also because of the serialization/deserialization operations that must take place.

Storing Session Data in SQL Server

It is possible to store session information in SQL Server using a set of predefined tables. This approach comes with the greatest robustness, but also with the least performance. However, for applications needing robust failover, this is the best option because you can take advantage of database clustering to deal with database failures.
To get set up for storing state information in SQL Server, you must
  1. Create the predefined database in SQL Server.
  2. Configure the web.config file to point to that SQL Server.

Creating the SQL Server State Database

This first step involves running Enterprise Manager and running a ready-made script to create the database and tables needed. There are two sets of script pairs:
  • InstallSqlState.sql—Creates the ASPState and TempDB databases. State information is maintained in the TempDB database, which only holds this information temporarily. If SQL Server is shut down, the data is lost.
  • UninstallSqlState.sql—Uninstalls the database created with InstallSqlState.sql.
  • InstallPersistSqlState.sql—Installs the ASPState database. This version of the database stores state information in the same database, and therefore state data is persistent.
  • UninstallPersistSqlState.sql—Uninstalls the database created with InstallPersistSqlState.sql.
You will find these scripts located in the following directory:
%SystemRoot%Microsoft.NETFramework[Framework Version]
Depending on which install script you chose to run, you should find the ASPState and possibly the TempDB databases in SQL Server through the Enterprise Manager.

Modifying web.config for SQLServer State Management

Once your database is set up, you need to modify the web.config file to point the ASP.NET application to the database for state management. The web.config should look similar to that shown here:
<configuration>
  <system.web>
    <sessionState mode="SQLServer" 
       sqlConnectionString="data source=localhost;user id=sa;pwd=somepwd" />
  </system.web>
</configuration>
Note the setting of the mode attribute to SQLServer. Additionally, you'll see the connection information provided so that a connection can be made to the database.

Session Events

Two events related to Sessions exist that you can handle. These are the Session_Start and Session_End events. The Session_Start event occurs when a user first visits the site. The Session_End event occurs when the user leaves the site or when the session times out. Both events are declared under the TGlobal class. This class is found in the Global.pas file included with your project.
One way to use these events is to maintain a running user count on your site. When a user visits, you up the count. When a user leaves, you decrement the count. Listing 33.8 shows how you might do this.

Listing 33.8  Storing a User Count in Session_Start

procedure TGlobal.Session_Start(sender: System.Object; e: EventArgs);
begin
  Application.Lock;
  try
    if Application['NumUsers'] = nil then
      Application['NumUsers'] := System.Object(Integer(1))
    else
      Application['NumUsers'] :=
        System.Object(Integer(Application['NumUsers'])+1);
  finally
    Application.UnLock;
  end;
end;

Find the code on the CD: CodeChapter 33Ex12.

Conversely, you would decrement the user count in the Session_End event as shown in Listing 33.9.

Listing 33.9  Storing a User Count in Session_End

1:   procedure TGlobal.Session_End(sender: System.Object; e: EventArgs);
2:   begin
3:     Application.Lock;
4:     try
5:       if Application['NumUsers'] <> nil then
6:         Application['NumUsers'] :=
7:           System.Object(Integer(Application['NumUsers'])-1)
8:      else
9:         Application['NumUsers'] := System.Object(Integer(0));
10:    finally
11:      Application.UnLock;
12:    end;
13:  end;

Find the code on the CD: CodeChapter 33Ex12.

You might have noticed that both these event handlers make use of the Application object, which I discuss in the next section.

Application State Management

Application state is different from session state in that data stored at the application level is available to all users of the application. In session state, session data is stored for the user of the session only. Figure 33.4 depicts this difference.

Figure 33.4  
Difference between application and session state.
Application state is maintained by the HttpApplicationState class. This class is a dictionary, and it is created upon the first request to the application. This is unlike the HttpSessionState class, which is created upon each user's visit to the site.
The HttpApplicationState class works very much like the HttpSessionState class.
Information you would store in the HttpApplicationState class needs to be available to all users of the applications. For instance, connection strings to the database and number of users signed on are examples of application-wide information.

Storing Information Using the Application Object

You can add, access, and remove data similarly to how you do so with the HttpSessionState class.
To add an item, simply do the following:
Application['NumUsers'] := System.Object(Integer(1));
To remove an item, call the HttpApplicationState.Remove() function like this:
Application.Remove('MyItem');
Accessing an item is equally as simple:
MyItem := Application['MyItem'];
You can clear the contents of the Application object by calling its RemoveAll() method:
Application.RemoveAll;

Synchronizing Access to State Data in the Application Object

The operations of the HttpApplicationState class are thread-safe, but if you intend to perform a group of operations, you might want to lock writing access to the application state until you are finished with your set of operations. Listings 33.8 and 33.9 illustrate using the Application.Lock() and Application.UnLock() methods for locking and unlocking write access to the data maintained by the Application object.
Note that it is necessary to match every Lock() call with an UnLock() call that is protected by a try-finally clause.
By doing this, you can prevent concurrent access, which can cause deadlocks, race conditions, and other problems.

Using Cache Versus Application

It might appear that there are close likenesses between the Cache and HttpApplicationState classes. Both have the capability to store data in an application-wide context, and the syntax for dealing with them is basically identical. The differences, however, are great.
Both the Cache and HttpApplicationState classes provide a mechanism for storing application-wide data and can be used for managing state because of this. This is where the likenesses end.
The Cache class takes management of this data further than that of the HttpApplicationClass.
First, accessing data in the Cache class is fully thread-safe. This is unlike the HttpApplicationState class, which requires you to surround data access with synchronization methods Lock() and UnLock().
Second, the Cache class, based on a prioritization scheme, can free data from the Cache when it has not been used in order to free up memory when resources are low.
Also, you get more control over the items added to the Cache by setting absolute and sliding expiration policies.
Last, you can associate items with the Cache to other cached items or to a file, which will result in the cached items being removed from the Cache.
The HttpApplicationState class serves well as a general state store for information needing to be available application-wide and needing to exist during the life of the application.

The Cache class is better suited for complex state management in which greater control over the cached data is required.

Receive All Free Updates Via Facebook.