Using Page Data Caching


In the previous sections of this chapter, you learned how to cache the entire contents of a page and a particular area of a page. However, in certain situations, caching the data displayed on a page makes more sense than caching the page itself.

Imagine that you are creating an intranet application that displays an employee directory for your company. Imagine that you need to display employee information on multiple pages. On one page, you might display a list of employee last names . On another page, you might want to display detailed information about a particular employee.

In this situation, you can store all the employee information in a database table and cache a DataSet that contains the database table in memory. On each page in which you need to display employee information, you can retrieve the information from the cached DataSet .

Caching database records can significantly improve the performance of your Web site. Instead of retrieving employee information from a database table every time you need to display the information on a page, you can retrieve the information directly from memory.

You can cache items in memory by taking advantage of the Cache object, which is an instance of the Cache class. Each ASP.NET application has a single Cache object that remains valid until the application is restarted.

ASP CLASSIC NOTE

When building pages with Classic ASP, developers often resorted to caching data in the Application object. This object still exists in ASP.NET, and you can still use it to cache data. However, the Cache object provides you with more flexibility than the Application object.

The Cache object enables you, for example, to specify expiration policies and dependencies for cached items. Furthermore, the Cache object supports scavenging. When you add items to the Application object, the item remains in memory until explicitly removed or the application is restarted. The Cache object, on the other hand, automatically dumps items from the cache when memory resources get low.


In the following sections, you learn different methods of adding and removing items from the Cache object.

Adding Items to the Cache

You can add any item to the Cache object by assigning a value to its Item property. For example, the following statement adds a new item named myItem with the value "Hello World!" to the Cache object:

 
 Cache.Item( "myItem" ) = "Hello World!" 

Because Item is the default property of the Cache object, you can also add an item to the cache like this:

 
 Cache( "myItem" ) = "Hello World!" 

You can add more complex objects to the cache, such as DataSets , DataViews , or collections. For example, the following statements add a HashTable to the Cache object:

 
 Dim colHashTable As HashTable colStates = New HashTable colStates.Add( "CA", "California" ) colStates.Add( "WA", "Washington" ) Cache( "States" ) = colStates 

After you add an item to the cache, you can retrieve it like this:

 
 myItem = Cache( "myItem" ) 

You need to understand that when you add items to the Cache object, the items are not guaranteed to remain there. The Cache object supports scavenging; it automatically removes low-priority or infrequently used items from the cache when system resources become low.

NOTE

See the section "Setting Cache Item Priorities" later in this chapter for more information on how to control when a cached item expires .


When you retrieve items from the cache, therefore, you should always check to see whether the item still exists before you do anything with it. If the item has been removed from the Cache object, attempting to retrieve the item from the cache returns the value Nothing .

Typically, when you retrieve items from the cache, you use the following logic:

 
 myItem = Cache( "myItem" ) If myItem Is Nothing Then   ... Recreate item and assign to Cache End If 

In other words, after you retrieve an item from the cache, you should check whether the item is Nothing . If it is Nothing , you need to reassign the item to the cache.

If you want to explicitly remove items from the cache, you can use the Cache object's Remove method. For example, the following statement removes the item named myItem from the cache:

 
 Cache.Remove( "myItem" ) 

The Cache object contains more than just the information you have added. The object also is used internally to cache system information between requests . You can view all the current items in the cache by using the page in Listing 17.14 (see Figure 17.5).

Listing 17.14 ShowCached.aspx
 <Script Runat="Server"> Sub Page_Load   Dim objItem As DictionaryEntry   For each objItem in Cache     lblContents.Text &= "<li>" & objItem.Key     lblContents.Text &= "=" & objItem.Value.ToString   Next End Sub </Script> <html> <head><title>ShowCached.aspx</title></head> <body> <h2>Cached Items</h2> <asp:Label   ID="lblContents"   EnableViewState="False"   Runat="Server" /> </body> </html> 

The C# version of this code can be found on the CD-ROM.

Figure 17.5. List of cached items.

graphics/17fig05.jpg

In the Page_Load subroutine in Listing 17.14, a FOR...EACH loop displays the key and value for each item in the Cache object. The items are displayed in a Label control.

Adding Cache File Dependencies

When you add an item to the cache, you can associate the item with a file. If the file changes, the item is automatically removed from the cache.

To create a file dependency, use the Insert method of the Cache object like this:

 
 Cache.Insert( "myItem", "Hello!", _   New CacheDependency( MapPath( "myFile.txt" ) ) ) 

This statement adds an item named myItem with the value Hello! to the cache. It also creates a file dependency between the item and a file named myFile.txt. If myFile.txt is modified, myItem is automatically dumped from the cache.

You also can create a file dependency between a cached item and multiple files. For example, the following statements make the cached item named myItem dependent on three files named File1.txt, File2.txt, and File3.txt:

 
 Dim myFiles() As String = _     { MapPath( "File1.txt" ), _       MapPath( "File2.txt" ), _       MapPath( "File3.txt" ) } Cache.Insert( "myItem", "Hello", _   New CacheDependency( myFiles ) ) 

A string array containing a list of three files is passed to the constructor of the CacheDependency class to create the file dependencies. If any of the three files are modified, myItem is dropped from the cache.

NOTE

The MapPath method of the Page class translates a virtual path , such as /myFile.txt , into a physical path, such as c:\inetpub\ wwwroot \myFile.txt .


Creating file dependencies is useful when you're working with custom configuration files. Imagine, for example, that your application depends on a custom XML file for configuration information. You use the XML file to specify format information for your pages, such as the background color and font size.

To improve the performance of your application, you can cache the XML file in the Cache object. However, if someone updates the XML configuration file, you want the cached version of the configuration file to be automatically updated as well. You can add this functionality by creating a file dependency between the cached XML file and the XML file on the hard drive.

The page in Listing 17.15 illustrates how you can create a file dependency between a cached item named FormatInfo and an XML file named FormatInfo.xml .

Listing 17.15 FileDependency.aspx
 <%@ Import Namespace="System.Data" %> <Script Runat="Server"> Dim strBgcolor As String Dim strFontface As String Dim strFontsize As String Sub Page_Load   Dim dstFormat As DataSet   Dim drowFormat As DataRow   dstFormat = Cache( "FormatInfo" )   If dstFormat Is Nothing Then     dstFormat = GetFormatInfo()     Cache.Insert( "FormatInfo", dstFormat, _       New CacheDependency( MapPath( "FormatInfo.xml" ) ) )   End If   drowFormat = dstFormat.Tables( 0 ).Rows( 0 )   strBgcolor = drowFormat( "bgcolor" ).Trim   strFontface = drowFormat( "fontface" ).Trim   strFontsize = drowFormat( "fontsize" ).Trim End Sub Function GetFormatInfo() As DataSet   Dim dstFormat As DataSet   dstFormat = New DataSet()   dstFormat.ReadXML( MapPath( "FormatInfo.xml" ) )   Return dstFormat End Function </Script> <html> <head> <basefont   size="<%=strFontsize%>"   face="<%=strFontface%>"> <title>FileDependency.aspx</title> </head> <body bgcolor="<%=strBgcolor%>"> Hello, welcome to our Web site! </body> </html> 

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 17.15, an item named FormatInfo is retrieved from the Cache object. The FormatInfo item represents the FormatInfo.xml file as a DataSet .

The background color, base font face, and base font size of the page are assigned values from the FormatInfo DataSet .

If the FormatInfo item does not exist in the cache, the GetFormatInfo() function is called to retrieve the FormatInfo.xml file from the hard drive and assign it to the cache. When the FormatInfo item is added to the cache, it's added with a file dependency on the FormatInfo.xml file, which is included in Listing 17.16.

Listing 17.16 FormatInfo.xml
 <format>   <bgcolor>   Green   </bgcolor>   <fontsize>   6   </fontsize>   <fontface>   Arial   </fontface> </format> 

The C# version of this code can be found on the CD-ROM.

If you modify the FormatInfo.xml file, the FormatInfo DataSet is automatically updated in the cache. For example, if you change the bgcolor element in the FormatInfo.xml file from Green to Red , the background color of the page is automatically updated.

Adding Cache Trigger Dependencies

It would be nice if you could update an item in the cache whenever a change is made to a record in a database table. That way, you could cache a database table in memory and dramatically improve the performance of your application without worrying about the data becoming outdated . Unfortunately, however, the Cache object does not directly support this type of dependency. To make an item in the cache dependent on a database table, you need to do a little work.

Microsoft SQL Server includes support for a special type of stored procedure called a trigger . A trigger is a stored procedure that executes whenever an update , delete , or insert statement is executed against a table or view. In other words, you can associate a trigger with a table in such a way that it fires whenever a change is made to the contents of the table.

Microsoft SQL Server also includes an extended stored procedure named xp_cmdshell that executes a string as an operating system command. You can use the xp_cmdshell stored procedure to write a file to a directory.

To create an item in the cache that is dependent on a database table, therefore, you can create a trigger that writes a file to a directory. You can then use a normal file cache dependency to update the item in the cache.

To illustrate how this scenario works, you can add a DataSet that represents the Products table to the cache. Whenever a change to the Products table is made, the Products table is automatically updated.

The first thing you need to do is create the trigger. The proper trigger is contained in Listing 17.17.

Listing 17.17 UpdateCache.sql
 CREATE TRIGGER UpdateCache ON Products FOR UPDATE, DELETE, INSERT AS DECLARE @cmd Varchar( 200 ) SELECT @cmd = 'echo ' + Cast( getDATE() As Varchar( 50 ) ) +   ' > c:\ leChange.txt' EXEC master..xp_cmdshell @cmd, no_output 

The C# version of this code can be found on the CD-ROM.

The UpdateCache trigger is fired when an update , delete , or insert statement is executed against the Products table. The trigger uses the xp_cmdshell stored procedure to write a file, named tableChange.txt, to the file system. The file contains the current date and time.

Next, you need to create an ASP.NET page that adds the Products database table to the Cache object. The page in Listing 17.18 adds a DataSet representing the Products table to the cache.

Listing 17.18 DatabaseDependency.aspx
 <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <Script Runat="Server"> Sub Page_Load   Dim dstProducts As DataSet   dstProducts = Cache( "productsDS" )   If dstProducts Is Nothing Then     dstProducts = GetProducts()     Cache.Insert( "Products", dstProducts, _       New CacheDependency( "c:\ leChange.txt" ) )   End If   dgrdProducts.DataSource = dstProducts   dgrdProducts.DataBind() End Sub Function GetProducts() As DataSet   Dim conNorthwind As SqlConnection   Dim strSelect As String   Dim dadProducts As SqlDataAdapter   Dim dstProducts As DataSet   conNorthwind = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret;Database=Northwind" )   strSelect = "Select TOP 20 * From Products ORDER BY ProductID"   dadProducts = New SqlDataAdapter( strSelect, conNorthwind )   dstProducts = New DataSet()   dadProducts.Fill( dstProducts, "ProductsDS" )   Return dstProducts End Function </Script> <html> <head><title>DatabaseDependency.aspx</title> </head> <body> <asp:DataGrid   ID="dgrdProducts"   Runat="Server" /> </body> </html> 

The C# version of this code can be found on the CD-ROM.

When you request the page in Listing 17.18, a DataSet representing the Products database table is retrieved from the cache. If the item doesn't exist in the cache, the DataSet is created and added to the cache with a file dependency on the tableChange.txt file.

The Products database table remains cached in memory until a change is made to the underlying Products database table. If the Products table is modified, the UpdateCache trigger executes and writes a file to the hard drive. This file, in turn , causes the current cached copy of the Products table to be dropped and a new copy of the table to be added to the cache.

Adding Cache Key Dependencies

You can make one cached item dependent on another cached item by creating a key dependency . For example, imagine that you have two database tables cached in memory. One table contains a list of products, and the other table contains a list of product categories. If you make a change to the cached product categories table, you also want to update the cached copy of the products table. You can do so by creating a key dependency.

To create a key dependency between one cached item and another, you use the Cache object's Insert method like this:

 
 Dim arrKeyDepends() As String = { "item2" } Cache.Insert( "item1", "hello!", _   New CacheDependency( Nothing, arrKeyDepends ) ) 

This statement makes item1 dependent on item2 . If item2 is modified, item1 is dumped from the Cache object. The first statement creates a string array of key names. In the second statement, this array is passed to the CacheDependency constructor to create the dependencies.

You can make one cached item dependent on multiple cached items. To do so, simply list multiple cached item keys in the array:

 
 Dim arrKeyDepends() As String = { "item2", "item3", "item4" } Cache.Insert( "item1", "hello!", _   New CacheDependency( Nothing, arrKeyDepends ) ) 

These statements make item1 dependent on item2 , item3 , and item4 . If any of these cached items are modified, item1 is automatically deleted from the cache.

The page in Listing 17.19 illustrates how to make one cached item dependent on another.

Listing 17.19 KeyDependency.aspx
 <Script Runat="Server"> Sub UpdateItem1( s As Object, e As EventArgs )   Cache( "item1" ) = txtNewValue.Text End Sub Sub UpdateItem2( s As Object, e As EventArgs )   Dim arrKeyDepends() As String = { "item1" }   Cache.Insert( "item2", txtNewValue.Text, _     New CacheDependency( Nothing, arrKeyDepends ) ) End Sub </Script> <html> <head><title>KeyDependency.aspx</title></head> <body> <form Runat="Server"> <asp:TextBox   ID="txtNewValue"   Runat="Server" /> <p> <asp:Button   Text="Update Item1"   OnClick="UpdateItem1"   Runat="Server" /> <asp:Button   Text="Update Item2"   OnClick="UpdateItem2"   Runat="Server" /> <hr> Item1 = <%=Cache( "item1" )%> <br> Item2 = <%=Cache( "item2" )%> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

The page in Listing 17.19 contains one TextBox and two Button controls. If you click the first button, the value of item1 in the cache is updated. If you click the second button, the value of item2 in the cache is updated.

A key dependency exists between item1 and item2 . If you update item1 , item2 is automatically dropped from the cache. You can test this functionality by entering a value in the TextBox control and clicking the Update Item1 button when Item2 has a value.

NOTE

Key dependencies cascade. If you make a chain of key dependencies, a change in one item in the chain causes the other items in the chain to be dropped. You also can make key dependencies circular. In that case, a modification to any item in the circle drops all the cached items in the circle.


Creating an Absolute Expiration Policy

When you add items to the cache, you can specify an absolute expiration policy. Items that are inserted with an absolute expiration policy are automatically dropped from the cache after a preset period of time.

The following statement, for example, adds to the cache an item that will automatically expire in one minute:

 
 Cache.Insert( "myItem", "Hello!", Nothing, _   DateTime.Now.AddMinutes( 1 ), Cache.NoSlidingExpiration ) 

This statement adds an item named myItem with the value Hello! to the cache. The item is inserted with no file or key dependencies, and an absolute expiration date and time of one minute in the future. (I discuss the Cache.NoSlidingExpiration parameter in the next section.)

The page in Listing 17.20 illustrates how to create an item with an absolute expiration policy within an ASP.NET page.

Listing 17.20 AbsoluteExpiration.aspx
 <Script Runat="Server"> Sub Page_Load   Dim strTime As String   strTime = Cache( "Time" )   If strTime Is Nothing Then     strTime = DateTime.Now.ToString( "T" )     Cache.Insert("Time", strTime, Nothing, _       DateTime.Now.AddMinutes( 1 ), Cache.NoSlidingExpiration )   End If   lblMessage.Text = strTime End Sub </Script> <html> <head><title>AbsoluteExpiration.aspx</title></head> <body> Data last cached: <asp:Label   ID="lblMessage"   Font-Bold="True"   ForeColor="Blue"   Runat="Server" /> </body> </html> 

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 17.20, an item named Time is pulled from the cache. If Time is Nothing , the item has expired . In that case, the item is added to the cache again with an absolute expiration policy of one minute in the future.

Creating a Sliding Expiration Policy

Instead of setting an absolute expiration policy for an item stored in the cache, you can set a sliding policy. When an item is cached with a sliding expiration policy, the item is removed from the cache a certain interval of time after it was last accessed.

The following statement adds an item that will expire one minute after it is last accessed:

 
 Cache.Insert( "myItem", "Hello!", Nothing, _   Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes( 1 ) ) 

This statement inserts into the cache an item that won't expire unless it is not accessed for more than one minute. If never more than a minute goes by without the item being accessed, the item will never be deleted from the cache (unless server resources get low).

Listing 17.21 illustrates how you can add an item with a sliding expiration policy in an ASP.NET page.

Listing 17.21 DataSlide.aspx
 <Script Runat="Server"> Sub Page_Load   Dim strTime As String   strTime = Cache( "Time" )   If strTime Is Nothing Then     strTime = DateTime.Now.ToString( "T" )     Cache.Insert("Time", strTime, Nothing, _       Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes( 1 ) )   End If   lblMessage.Text = strTime End Sub </Script> <html> <head><title>DataSlide.aspx</title></head> <body> Data last cached: <asp:Label   ID="lblMessage"   Font-Bold="True"   ForeColor="Blue"   Runat="Server" /> </body> </html> 

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 17.21, an item is inserted into the cache with a sliding expiration of 1 minute. If you repeatedly refresh the page, the item is never removed from the cache.

NOTE

You cannot add an item to the cache with both an absolute and sliding expiration policy.


Setting Cache Item Priorities

When you insert an item into the cache, you can insert the item with a certain priority. The priority provides the Cache object with a hint about the relative importance of an item. All things being equal, when times are tough and server resources become low, the cache starts dropping lower priority items before higher priority items.

You indicate the relative priority of an item by using one of the following values from the CacheItemPriority enumeration:

  • AboveNormal ” Items with this priority are less likely to be removed from the cache than Normal .

  • BelowNormal ” Items with this priority are more likely to be removed from the cache than Normal .

  • Default ” The Default value is Normal .

  • High ” Items with this priority are the least likely to be removed from the cache.

  • Low ” Items with this priority are the most likely to be removed from the cache.

  • Normal ” These items have the Normal priority.

  • NotRemovable ” Items with this priority should never be removed from the cache.

To insert an item into the cache with a High priority, for example, you would use the following statement:

 
 Cache.Insert( "myItem", _               "Hello", _                Nothing, _                Cache.NoAbsoluteExpiration, _                Cache.NoSlidingExpiration, _                CacheItemPriority.High, _                Nothing ) 

This statement provides a hint to the Cache object to weigh this cache item higher than other cache items.

Creating a Cache Callback Method

When you insert an item into the cache, you can associate a callback method with the item. If, for whatever reason, the item is removed from the cache, the callback method automatically executes.

A callback method is useful in several applications. The most obvious use for a callback method is for automatically reloading an item into the cache when it expires. You also can create more complicated applications for a callback method.

Imagine, for example, that you are displaying banner advertisements at your Web site, and you want the list of banner advertisements to be automatically reloaded from a database table every hour . In that case, you could add an absolute expiration policy to the cached item and create a callback method that reloads the banner advertisements.

You also can use the callback method to track the behavior of the Cache object. You can write a callback method that writes to a log file every time an item is dropped from memory.

To create a callback method, you need to add a subroutine to an ASP.NET page that accepts three parameters: String , Object , and CacheItemRemovedReason . The callback method should look like this:

 
 Sub ItemRemoved( itemKey As String, _                  itemValue As Object, _                  removedReason As CacheItemRemovedReason ) End Sub 

The first two parameters passed to the callback method represent the name and value of the item removed from the cache.

The last parameter represents a value from the CacheItemRemovedReason enumeration. This enumeration, which indicates the reason the item was removed from the cache, can have the following values:

  • DependencyChanged ” Removed because of a file or key dependency

  • Expired ” Removed because of an expiration policy

  • Removed ” Removed with an explicit call to either the Remove or Insert method

  • Underused ” Removed because of low server resources

After you create the callback method, you need to create an instance of the CacheItemRemovedCallback class and initialize the class with the callback method. For example, the following statements create an instance of the CacheItemRemovedCallback class named onRemove :

 
 Dim Shared onRemove As CacheItemRemovedCallback onRemove =  New CacheItemRemovedCallback( AddressOf ItemRemoved ) 

Finally, you need to refer to the onRemove object as follows when you insert an item into the cache:

 
 Cache.Insert( "myItem", _               "Hello!", _               Nothing, _               Cache.NoAbsoluteExpiration, _               Cache.NoSlidingExpiration, _               CacheItemPriority.High, _               onRemove ) 

Notice that you specify the callback method with the last parameter passed to the Insert method. In this case, you have created an item that does not have any file or key dependencies, or an absolute or sliding expiration date.

The page in Listing 17.22 demonstrates how you can use a callback method in an ASP.NET page.

Figure 17.6. Using a cache callback method.

graphics/17fig06.jpg

Listing 17.22 CacheCallback.aspx
 <Script Runat="Server"> Public Shared onRemove As CacheItemRemovedCallback Sub ItemRemoved( _   strItemKey As String, _   objItemValue As Object, _   objRemovedReason As CacheItemRemovedReason )   Dim strLogEntry As String   strLogEntry = "Item with value " & objItemValue.ToString()   strLogEntry &= " removed at " & Now.ToString( "T" )   strLogEntry &= " because of " & objRemovedReason.ToString()   If Application( "CacheLog" ) Is Nothing Then     Application( "CacheLog" ) = New ArrayList   End If   Application( "CacheLog" ).Add( strLogEntry )   Beep End Sub Sub btnAddCache_Click( s As Object, e As EventArgs )     onRemove =  New CacheItemRemovedCallback( AddressOf ItemRemoved )     Cache.Insert( "myItem", _                    txtNewValue.Text, _                    Nothing, _                    Now.AddSeconds( 10 ), _                    Cache.NoSlidingExpiration, _                    CacheItemPriority.High, _                    onRemove ) End Sub Sub btnRemoveCache_Click( s As Object, e As EventArgs )   Cache.Remove( "myItem" ) End Sub Sub Page_PreRender( s As Object, e As EventArgs )   dgrdCacheLog.DataSource = Application( "CacheLog" )   dgrdCacheLog.Databind() End Sub </Script> <html> <head><title>CacheCallback.aspx</title></head> <body> <form Runat="Server"> <h2>Cache Log</h2> <asp:DataGrid   ID="dgrdCacheLog"   CellPadding="8"   Runat="Server" /> <p> <asp:TextBox   id="txtNewValue"   Runat="Server" /> <p> <asp:Button   id="btnAddCache"   Text="Add To Cache!"   OnClick="btnAddCache_Click"   Runat="Server" /> <asp:Button   id="btnRemoveCache"   Text="Remove From Cache!"   OnClick="btnRemoveCache_Click"   Runat="Server" /> <asp:Button   Text="Refresh Page!"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

The page in Listing 17.22 contains a form that enables you to add and remove items to the cache. When you add an item, the item is added with an absolute expiration policy of 10 seconds and associated with a callback method named itemRemoved . This callback method does two things:

  1. The method adds an entry to a log that includes the time and reason the item was removed.

  2. The method calls the Visual Basic Beep statement to create a beep sound.

The page contains a DataGrid that displays the contents of the cache log. The DataGrid displays the reason that each item was removed from the cache.

NOTE

It would not be a good idea to use the Beep statement in a production application. I've used the statement here so you can detect when an item is expired from the cache.


You see two types of entries in the cache log. If the item expires from the cache, the log entry contains the word Expired . If you explicitly remove an item by clicking the Remove From Cache! button, the log entry contains the word Removed .



ASP.NET Unleashed
ASP.NET 4 Unleashed
ISBN: 0672331128
EAN: 2147483647
Year: 2003
Pages: 263

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