Section 17.4. Object Caching

17.4. Object Caching

All the examples in the previous section have cached pages, or parts of pages wrapped in user controls. But ASP.NET allows you much more caching flexibility. You can use object caching to place any object in the cache. The object can be of any type: a data type, a web control, a class, a DataSet , and so on.

The object cache is stored in server memory, a limited resource, and the careful developer will conserve that resource. That said, it is an easy way to buy significant performance benefits when used wisely, especially since ASP.NET will evict older items if memory becomes scarce .

Suppose you are developing a retail shopping catalogue web application. Many of the page requests contain queries against the same database to return a relatively static price list and description data. Instead of your control querying the database each time the data is requested , the data set is cached, so subsequent requests for the data will be satisfied from the high-speed cache rather than forcing a relatively slow and expensive regeneration of the data. You might want to set the cache to expire every minute, hourly, or daily, depending on the needs of the application and the frequency with which the data is likely to change.

Object caching is implemented by the Cache class. One instance of this class is created automatically per application when the application starts. The class remains valid for the life of the application. The Cache class uses syntax very similar to that of session and application state. Objects are stored in Cache as key/value pairs in a dictionary object. The object being stored is the value, and the key is a descriptive string.

The next example, ObjectCaching, will clarify object caching . A web page will display a GridView containing data from the Northwind database. It will initially query data from the Northwind database into a DataSet and then store the DataSet in cache for subsequent requests.

Create a new web site and call it ObjectCaching . Drag a Label , called lblMessage , a GridView , called gv , and two Buttons onto the form. The complete listing of the content file is shown in Example 17-19, with the controls highlighted.

Example 17-19. Default.aspx for ObjectCaching
 <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs"    Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server">     <title>Object Caching</title> </head> <body>     <form id="form1" runat="server">     <div>       <h1>Object Caching</h1>  <asp:Label ID="lblMessage" runat="server" />  <br />        <br />  <asp:GridView ID="gv" runat="server" />  <br />  <asp:Button ID="btnClear" runat="server"             Text="Clear Cache"             OnClick="btnClear_Click" />        <asp:Button ID="btnPost" runat="server" Text="Post" />  </div>     </form> </body> </html> 

It would simplify matters greatly if you could just use a SqlDataSource control, set the EnableCache property of that control, and then set the DataSource property of the GridView to point to the SqlDataSource . However, there are certain circumstances in which you must "manually" create the data source and bind the source to the control. This is covered in detail in Chapter 10, but two of these circumstances are when you need to implement connection-based transactions or you are building an n- tier data architecture and your data is being retrieved from a business object. Example 17-19 demonstrates the principles involved in caching an arbitrary object.

Looking at the directive at the top of the content file, no OutputCache directive exists since this example does not use output caching.

The associated code-behind file is listed in Example 17-20, with an analysis to follow.

Example 17-20. Default.aspx.cs for ObjectCaching
 using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Data.SqlClient;      // necessary for SqlDataAdapter public partial class _Default : System.Web.UI.Page {     protected void Page_Load(object sender, EventArgs e)     {        CreateGridView(  );     }     private void CreateGridView(  )     {        DataSet dsGrid;        dsGrid = (DataSet)Cache["GridViewDataSet"];        if (dsGrid == null)        {           dsGrid = GetDataSet(  );           Cache["GridViewDataSet"] = dsGrid;           lblMessage.Text = "Data from database.";        }        else        {           lblMessage.Text = "Data from cache.";        }        gv.DataSource = dsGrid.Tables[0];        gv.DataBind(  );     }     private DataSet GetDataSet(  )     {        // connect to the Northwind database        string connectionString = "Data Source=MyServer;            Initial Catalog=Northwind;Persist Security Info=True;            User ID=sa;Password=secret";        // get records from the Customers table        string commandString = "Select top 10 CustomerID, CompanyName,                   ContactName, City from Customers";        // create the data set command object and the DataSet        SqlDataAdapter dataAdapter = new SqlDataAdapter(commandString,                             connectionString);        DataSet dsData = new DataSet(  );        // fill the data set object        dataAdapter.Fill(dsData, "Customers");        return dsData;     }    protected void btnClear_Click(object sender, EventArgs e)     {       Cache.Remove("GridViewDataSet");       CreateGridView(  );     } } 

The heart of the ObjectCaching example involves data access. For a complete discussion of data access using ADO.NET in ASP.NET, see Chapter 10. For now, notice the using System.Data.SqlClient statement added at the top of the code-behind file to allow working with the SqlDataAdapter class without typing fully qualified namespaces.

A method named CreateGridView is called every time the grid needs to be created. It is called in the Page_Load every time the page is loaded, as well as when the Clear Cache button is clicked.

Looking at the CreateGridView method, a DataSet object is instantiated to contain the data that will be bound and displayed by the grid:

 DataSet dsGrid; 

The Cache object with the key GridViewDataSet is then retrieved and assigned to the dsGrid DataSet object:

 dsGrid = (DataSet)Cache["DataGridDataSet"]; 

As with the Session and Application objects seen in Chapter 6, whatever is retrieved from the Cache object must be explicitly cast , or converted, to the correct data type, in this case DataSet . For this purpose, C# uses an explicit cast.

You can use the Cache, Session, and View syntax even from within global.asax files. (See Chapter 18 for a complete discussion on the global.asax file.) However, in that case, you must qualify the keyword with the current context:

 dsGrid = (DataSet)HttpContext.Current.Cache[                    "GridViewDataSet"]; 


The dsGrid data set is then tested to see if it actually exists. Though the DataSet object has been instantiated, it is only a placeholder until it actually contains data. If the Cache object with the key GridViewDataSet has not been created or has expired , then dsGrid still has no data in it.

 if (dsGrid == null) 

If the DataSet object already contains data, meaning the Cache had been previously filled and had not expired, the Label control's Text property will be set accordingly to convey this to you on the web page. Otherwise, the GetdataSet method is called, the cache is filled with the data set returned by GetdataSet , and the Label control's Text property is set accordingly:

 dsGrid = GetDataSet(  );     Cache["GridViewDataSet"] = dsGrid;     lbl.Text = "Data from database."; 

In either case, once the Data Set is filled, the DataSource property of the GridView control on the web page is set to be the first DataTable in the DataSet and the GridView control is data bound:

 dg.DataSource=dsGrid.Tables[0];     dg.DataBind(  ); 

The result of running ObjectCaching is shown in Figure 17-5.

Figure 17-5. ObjectCaching

The first time the web page is run, the label just above the GridView control will indicate that the data is coming directly from the database. Every subsequent time the form is requested, the label will change to "Data from cache."

There is no way for the cache, in this example, to expire unless memory becomes scarce on the server and ASP.NET removes it automatically. As you will see shortly, there are several ways to force a cache to expire. In this example, however, even opening a new browser instance on a different machine will cause the data to come from the cache unless the application on the server is restarted. That is because the cache is available to the entire application just as the Application object is.

In this example, a button, called btnClear , is added to the form to empty the cache and refill it. The event handler for this button calls the Cache.Remove method. This method removes the cache record specified by the key named as the parameter to the method.

 Cache.Remove("GridViewDataSet"); 

In Example 17-20, the button event handler refills the cache by calling the CreateGridView method. As an exercise in observing different behavior, comment out the line that calls CreateGridView in the btnClear_OnClick event procedure and observe the different behavior when you repost the page after clicking the Clear Cache button. When the line calling the CreateGridView method is not commented out, the next time a browser is opened after the Clear Cache button is clicked, the data will still come from the cache. But if the line is commented out, the next browser instance will get the data directly from the database.

17.4.1. Cache Class Functionality

The previous example, ObjectCaching , demonstrates how to add values to and retrieve values from the Object cache using a dictionary syntax of key/value pairs. The Cache class exposes much more functionality than this, including the ability to set dependencies, manage expirations, and control how memory used by cached objects can be recovered for more critical operations. All of these features will be covered in detail in the next sections.

This additional functionality is exposed through a different syntax for adding objects to the cache that uses the Add and Insert methods of the Cache class. The Add and Insert methods are very similar in effect. The only difference is that the Add method requires parameters for controlling all the exposed functionality, and the Insert method allows you to make some of the parameters optional, using default values for those parameters.

The syntax for the Add method is:

 Cache.Add(        KeyName,        KeyValue,        Dependencies,        AbsoluteExpiration,        SlidingExpiration,        Priority,        CacheItemRemovedCallback); 

In this syntax, KeyName is a string with the name of the key in the Cache dictionary, and KeyValue is the object, of any type, to be inserted into the cache. All the other parameters will be described below.

The syntax examples for the overloaded Insert methods are:

  • To insert a key/value pair with default values for all the other parameters:

     Cache.Insert(   KeyName   ,   KeyValue   ); 

  • To insert a key/value pair with dependencies and with default values for the other parameters:

     Cache.Insert(KeyName, KeyValue, Dependencies); 

  • To insert a key/value pair with dependencies and expiration policies and with default values for the other parameters:

     Cache.Insert(KeyName, KeyValue, Dependencies, AbsoluteExpiration, SlidingExpiration); 

  • To insert a key/value pair with dependencies, expiration policies, and priority policy, and a delegate to notify the application when the inserted item is removed from the cache:

     Cache.Insert(KeyName, KeyValue, Dependencies, AbsoluteExpiration,     SlidingExpiration, Priority, CacheItemRemovedCallback); 

To see this syntax in action, replace a single line from Example 17-20. Find the line in the CreateGridView method that looks like this:

 Cache["GridViewDataSet "] = dsGrid; 

Replace it with the following:

 Cache.Insert("GridViewDataSet ", dsGrid); 

On running the modified page in a browser, you will see no difference from the prior version.

By using the Insert method rather than the Add method, you are only required to provide the key and value, just as with the dictionary syntax.

17.4.2. Dependencies

One useful feature exposed by the Cache class is dependencies. A dependency is a relationship between a cached item and a point in time or an external object. If the designated point in time is reached or if the external object changes, the cached item will expire and be removed from the cache.

The external object controlling the dependency can be a file, a directory, an array of files or directories , another item stored in the cache (represented by its key), or an array of items stored in the cache. The designated point in time can be either an absolute time or a relative time. In the following sections, we'll examine each of these dependencies and how they can be used to control the contents of the cache programmatically.

One of the more useful dependencies is data dependency, new to Version 2.0 of ASP.NET, where the cache is expired if the data in the underlying SQL Server database change. This feature is not part of object caching but is available to DataSourceControls and output caching. Data dependencies were covered earlier in this chapter.


17.4.2.1. File change dependency

With a file change dependency , a cached item will expire and be removed from the cache if a specified file changes. This feature is typically used when a cached data set is derived from an XML file. You do not want the application to get the data set from the cache if the underlying XML file has changed.

To generate an XML file containing the first five records from the Customers table of the Northwind database, an excerpt of which is listed in Example 17-21, perform the following steps:

  1. Use Start Programs Microsoft SQL Server Configure SQL XML Support in IIS.

  2. Use the following URL in a browser:

     http://localhost/     Northwind?sql=select+top+5*+from+Customers+for+xml+auto&root=ROOT 

  3. Copy the contents of the browser window into a text file called Northwind.xml .

  4. Add the first line in the XML file, shown in Example 17-21, specifying the UTF-16 character set. This 16-bit character set allows the higher-order characters common to many non-English languages.

Example 17-21. Excerpt from Northwind.xml (with line breaks added for readability)
  <?xml version="1.0" encoding="UTF-16" ?>  <ROOT>   <Customers CustomerID="ALFKI" CompanyName="Alfreds Futterkiste"     ContactName="Dan Hurwitz" ContactTitle="Sales Representative"     Address="Obere Str. 57" City="Berlin" PostalCode="12209"     Country="Germany" Phone="030-0074321" Fax="030-0076545" /> . . . </ROOT> 

The next example, ObjectCachingFileDependency , will use this XML file as a data source. You can then edit the XML file to demonstrate a file change dependency. To create this next example, copy the previous example, ObjectCaching , to a new web site, called ObjectCachingFileDependency .

The content file, default.aspx , is unchanged (unless you want to add a descriptive heading). Copy the XML file you have created into the root directory of the application. (Though this is not strictly necessary, it simplifies the code.)

In the code-behind file, you will modify the GetdataSet method to populate the dataset from the XML file rather than from the Northwind database, and you will modify the CreateGridView method to implement a Cache object with a file change dependency. The complete code-behind file is listed in Example 17-22, with the modified code highlighted.

Example 17-22. Default.aspx.cs for ObjectCachingFileDependency
 using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls;  using System.Web.Caching;          //  necessary for CacheDependency using System.Xml;                  //  necessary for Xml stuff  public partial class _Default : System.Web.UI.Page {     protected void Page_Load(object sender, EventArgs e)     {        CreateGridView(  );     }     private void CreateGridView(  )     {        DataSet dsGrid;        dsGrid = (DataSet)Cache["GridViewDataSet"];        if (dsGrid == null)        {           dsGrid = GetDataSet(  );  CacheDependency fileDepends =                 new CacheDependency(Server.MapPath("Northwind.xml"));           Cache.Insert("GridViewDataSet", dsGrid, fileDepends);           lblMessage.Text = "Data from XML file.";  }        else        {           lblMessage.Text = "Data from cache.";        }        gv.DataSource = dsGrid.Tables[0];        gv.DataBind(  );     }     private DataSet GetDataSet(  )     {  DataSet dsData = new DataSet(  );        XmlDataDocument doc = new XmlDataDocument(  );        doc.DataSet.ReadXml(Server.MapPath("Northwind.xml"));        dsData = doc.DataSet;  return dsData;     }    protected void btnClear_Click(object sender, EventArgs e)     {       Cache.Remove("GridViewDataSet");       CreateGridView(  );     } } 

In the using statements at the beginning of the file, you can delete the reference to System.Data.SqlClient since the code is no longer using any members of that namespace. Instead, add the two highlighted using statements from Example 17-22: one for caching and one for XML.

The goal of the GeTDataSet method is still to return a dataset. However, the source of the data for the dataset is now the XML file called Northwind.xml . Since ASP.NET stores datasets internally as XML, moving back and forth between XML and datasets is easy. The XML object equivalent to a dataset is the XmlDataDocument . An XmlDataDocument object named doc is instantiated. This XmlDataDocument object is filled using the ReadXml method. The MapPath method maps a virtual path of a file on the server to a physical path .

The DataSet object is obtained from the DataSet property of the XmlDataDocument object, then returned to the calling method.

In the CreateGridView method, only three lines have changed from the original ObjectCaching example. A CacheDependency object is defined against the source XML file. Again, MapPath is used to map the virtual path to a physical path.

The dictionary syntax used in the original ObjectCaching example to add the item to the cache is changed to use the Insert method of the Cache class. Using the Insert method allows you to specify a dependency in addition to the key name and value.

The text string assigned to the label has been updated to reflect that the data is now coming from an XML file rather than a database.

When you run this page, you will get something similar to Figure 17-6.

If you repost the page by highlighting the URL and pressing Enter, the label at the top of the page will indicate the data is coming from the cache.

Now open the Northwind.xml file in a text editor and make a change to one of the values in one of the records. Remember to save the XML file. When you repost the page in the browser, instead of the data still coming from the cache, it will once again be coming from the XML file.

As soon as the XML source file was changed, the cached data set was expired and removed from the cache. The next time the page requested the data set from the server, it had to retrieve it fresh from the XML file.

If you want the cache dependency to monitor an array of files or directories, the syntax for the CacheDependency constructor in Example 17-22 can take an array of file paths or directories rather than a single filename. So, for example, the single line of

Figure 17-6. ObjectCachingFileDependency

code in Example 17-22 that defines the CacheDependency object would be preceded by code defining a string array with one or more files or paths, and the CacheDependency constructor itself would take the array as a parameter. It would look like this:

 string[] fileDependsArray = {Server.MapPath("Customers.xml"),                                 Server.MapPath("Employees.xml")};     CacheDependency fileDepends = new CacheDependency(fileDependsArray); 

17.4.2.2. Cached item dependency

A cached item can be dependent on other items in the cache. If a cached item is dependent on one or more other cached items, it will be expired and removed from the cache if any of those cached items upon which it depends change. These changes include either removal from the cache or a change in value.

To make a cached item dependent on other cached items, the keys of all of the controlling items are put into an array of strings. This array is passed in to the CacheDependency constructor, along with an array of file paths. (If you do not want to define a dependency on any files or paths, then the array of file paths can be null .)

This is demonstrated in the next example, ObjectCachingItemDependency . To create this example, copy the previous example, ObjectCachingFileDependency , to a new web site with the new example name.

In ObjectCachingItemDependency , two buttons are added to the UI. The first button initializes several other cached items. The second button changes the value of the cached text string in one of the controlling cached items. As with the previous examples, a label near the top of the page indicates if the data was retrieved directly from an XML file or from cache. The Clear Cache and Post buttons are unchanged.

The complete content file for ObjectCachingItemDependency is listed in Example 17-23, with the code that is changed from the previous example highlighted.

Example 17-23. Default.aspx for ObjectCachingItemDependency
 <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs"    Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server">     <title>Object Caching</title> </head> <body>     <form id="form1" runat="server">     <div>       <h1>Object Caching</h1>       <h2>Item Dependency</h2>        <asp:Label ID="lblMessage" runat="server" />        <br />        <br />        <asp:GridView ID="gv" runat="server" />        <br />        <asp:Button ID="btnClear" runat="server" Text="Clear Cache"                    OnClick="btnClear_Click" />        <asp:Button ID="btnPost" runat="server" Text="Post" />        <br />        <br />  <asp:Button ID="btnInit" runat="server" Text="Initialize Keys"                    OnClick="btnInit_Click" />        <asp:Button ID="btnKey0" runat="server" Text="Change Key 0"                    OnClick="btnKey0_Click" />  </div>     </form> </body> </html> 

The code-behind file, listed in Example 17-24, modifies only the CreateGridView method from the previous example and adds Click event handlers for the two new buttons. The changes in Example 17-24 are highlighted.

Example 17-24. Default.aspx.cs for ObjectCachingItemDependency
 using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Web.Caching;          //  necessary for CacheDependency using System.Xml;                  //  necessary for Xml stuff public partial class _Default : System.Web.UI.Page {     protected void Page_Load(object sender, EventArgs e)     {        CreateGridView(  );     }     private void CreateGridView(  )     {        DataSet dsGrid;        dsGrid = (DataSet)Cache["GridViewDataSet"];        if (dsGrid == null)        {           dsGrid = GetDataSet(  );  string[] fileDependsArray = {Server.MapPath("Northwind.xml")};           string[] cacheDependsArray = {"Depend0", "Depend1", "Depend2"};           CacheDependency cacheDepends = new CacheDependency                                 (fileDependsArray, cacheDependsArray);           Cache.Insert("GridViewDataSet", dsGrid, cacheDepends);  lblMessage.Text = "Data from XML file.";        }        else        {           lblMessage.Text = "Data from cache.";        }        gv.DataSource = dsGrid.Tables[0];        gv.DataBind(  );     }     private DataSet GetDataSet(  )     {        DataSet dsData = new DataSet(  );        XmlDataDocument doc = new XmlDataDocument(  );        doc.DataSet.ReadXml(Server.MapPath("Northwind.xml"));        dsData = doc.DataSet;        return dsData;     }    protected void btnClear_Click(object sender, EventArgs e)     {       Cache.Remove("GridViewDataSet");       CreateGridView(  );     }  protected void btnInit_Click(object sender, EventArgs e)    {       //  Initialize caches to depend on.       Cache["Depend0"] = "This is the first dependency.";       Cache["Depend1"] = "This is the 2nd dependency.";       Cache["Depend2"] = "This is the 3rd dependency.";    }    protected void btnKey0_Click(object sender, EventArgs e)    {       Cache["Depend0"] = "This is a changed first dependency.";    }  } 

In the btnInit_Click event handler, the controlling cache items are created. The values of the cached items are unimportant for this example, except as something to change when the Change Key 0 button is clicked, which is done in the event handler for that button, btnKey0_OnClick .

The real action here occurs in the CreateGridView method. Two string arrays are defined, one to hold the file to depend upon, and one to hold the keys of the other cached items to depend upon.

The file dependency is exactly as described in the preceding section. If you do not wish to implement any file or directory dependency here, then use null :

 CacheDependency cacheDepends = new CacheDependency(null,                                                    cacheDependsArray); 

Running the ObjectCachingItemDependency example brings up the page shown in Figure 17-7. Initially, the label above the data grid will show that the data is from the XML file. After you click the Initialize Keys button, clicking the Post button or re-entering the URL will cause the data to come from the cache. Clicking any of the other buttons or changing the contents of Northwind.xml will cause the cached data set to expire and the data to be retrieved fresh from the XML file the next time the page is posted. Though this example does not explicitly demonstrate what would happen if one of the controlling cached items was removed from the cache, that, too, would cause the dependent cached item to expire.

17.4.2.3. Time dependency

Items in the cache can be given a dependency based on time. This is done with two parameters in either the Add or Insert methods of the Cache object.

Figure 17-7. ObjectCachingItemDependency

The two parameters that control time dependency are AbsoluteExpiration and SlidingExpiration . Both parameters are required in the Add method and are optional in the Insert method through method overloading.

To insert a key/value pair into the cache with file or cached item dependencies and time-based dependencies, use the following syntax:

 Cache.Insert(KeyName, KeyValue, Dependencies, AbsoluteExpiration, SlidingExpiration); 

If you don't want any file or cached item dependencies, then the Dependencies parameter should be null . If this syntax is used, default values will be used for the scavenging and callback parameters (described in the next sections).

The AbsoluteExpiration parameter is of type DateTime . It defines a lifetime for the cached item. The time provided can be an absolute time, such as August 21, 2006, at 1:23:45 P.M. The code to implement that type of absolute expiration would look something like the following:

 DateTime expDate = new DateTime(2006,8,21,13,23,45);     Cache.Insert("GridViewDataSet", dsGrid, null, expDate,                   Cache.NoSlidingExpiration); 

Obviously, this is not very flexible. Of greater utility is an absolute expiration based on the current time, say 30 minutes from now. The syntax for that expiration would be the following:

 Cache.Insert("GridViewDataSet ", dsGrid, null,                  DateTime.Now.AddMinutes(30), Cache.NoSlidingExpiration); 

This line of code inserts the specified data set into the cache and then expires that item 30 minutes after it was inserted. This scenario would be useful when you're accessing a slowly changing database where it was only necessary to ensure the data presented was no more than 30 minutes old.

Suppose that the data was volatile or needed to be very current. Perhaps the data presented must never be more than 10 seconds old. The following line of code implements that scenario:

 Cache.Insert("DataGridDataSet", dsGrid, null,                  DateTime.Now.AddSeconds(10), Cache.NoSlidingExpiration); 

If your web page is receiving hundreds of hits per minute, implementing a 10-second cache would provide a huge performance boost by reducing the number of database queries by a factor of 20 or more. Even a one-second cache can provide a significant performance enhancement to heavily trafficked web servers.

The other time-based parameter is SlidingExpiration , of type TimeSpan . This parameter specifies a time interval between when an item is last accessed and when it expires. If the sliding expiration is set for 30 seconds, for example, then the cached item will expire if the cache is not accessed within 30 seconds. If it is accessed within that time period, the clock will be reset, so to speak, and the cached item will persist for at least another 30 seconds. If the cache were accessed every 29 seconds, for example, it would never expire. You might use this for data which does not change often but is not used often either. To implement this scenario, use the following line of code:

 Cache.Insert("DataGridDataSet", dsGrid, null,                  Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(30)); 

Cache.NoAbsoluteExpiration is used for the AbsoluteExpiration parameter. Alternatively, you could use DateTime.MaxValue . This constant is the largest possible value of DateTime , corresponding to 11:59:59 P.M., 12/31/9999. (That's a millennium problem we can live with.) This indicates to ASP.NET that absolute expiration should not be used. If you attempt to use both types of expiration policies at once (absolute and sliding), an error will occur.

17.4.3. Scavenging

ASP.NET can scavenge memory by removing seldom-used or low-priority items from the cache if server memory falls below a given threshold. Doing so frees up memory to handle a higher volume of page requests.

Scavenging is influenced by the Priority parameter of the Add and Insert methods of the Cache class. This parameter is required of the Add method and optional for the Insert method

The Priority parameter indicates the value of the cached item relative to the other items stored in the cache. This parameter is used by the cache when it evicts objects to free up system memory when the web server runs low on memory. Cached items with a lower priority are evicted before items with a higher priority.

The legal values of the Priority parameter are contained in the CacheItemPriority enumeration, as shown in Table 17-4 in descending order of priority.

Table 17-4. Members of the CacheItemPriority enumeration

Priority value

Description

NotRemovable

Items with this priority will not be evicted.

High

Items with this priority level are the least likely to be evicted.

AboveNormal

Items with this priority level are less likely to be evicted than items assigned Normal priority.

Default

This is equivalent to Normal .

Normal

The default value.

BelowNormal

Items with this priority level are more likely to be evicted than items assigned Normal priority.

Low

Items with this priority level are the most likely to be evicted.


To implement scavenging , use the following line of code:

 Cache.Insert("DataGridDataSet", dsGrid, null,                  Cache.NoAbsoluteExpiration,                  Cache.NoSlidingExpiration,                  CacheItemPriority.High,                  null); 

The final parameter in the above lines of code pertains to callback support , which will be covered in the next section.

Since the Insert method calls use all seven parameters, you could use the Add method with the same parameters.

17.4.4. Callback Support

A CacheItemRemovedCallback event is raised when an item is removed from the cache for any reason. You may want to implement an event handler for this event, perhaps to reinsert the item into the cache or to log the event to evaluate whether your server needs more memory. The Add and Insert methods take a parameter that specifies the event handler (callback) method.

The next example demonstrates using the CacheItemRemovedCallback event. This example will behave identically to the previous example, shown in Figure 17-7, except that it will make an entry in a log file, hardcoded to be in c:\test.txt , every time the cache is expired.

Create a new web site called ObjectCachingCallback by copying the previous example, ObjectCachingItemDependency . The content file is functionally unchanged from the previous example. The code-behind file, listed in Example 17-25, has a number of changes, all of which are highlighted and then described in the analysis following the example.

Example 17-25. ObjectCachingCallback
 using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Web.Caching;         //  necessary for CacheDependency using System.Xml;                  //  necessary for Xml stuff public partial class _Default : System.Web.UI.Page {  public static CacheItemRemovedCallback onRemove = null;  protected void Page_Load(object sender, EventArgs e)     {        CreateGridView(  );     }     private void CreateGridView(  )     {        DataSet dsGrid;        dsGrid = (DataSet)Cache["GridViewDataSet"];  onRemove = new CacheItemRemovedCallback(this.RemovedCallback);  if (dsGrid == null)        {           dsGrid = GetDataSet(  );           string[] fileDependsArray = {Server.MapPath("Northwind.xml")};           string[] cacheDependsArray = {"Depend0", "Depend1", "Depend2"};           CacheDependency cacheDepends = new CacheDependency                                 (fileDependsArray, cacheDependsArray);           Cache.Insert("GridViewDataSet", dsGrid, cacheDepends,  DateTime.Now.AddSeconds(10),                         Cache.NoSlidingExpiration,                         CacheItemPriority.Default,                         onRemove);  lblMessage.Text = "Data from XML file.";        }        else        {           lblMessage.Text = "Data from cache.";        }        gv.DataSource = dsGrid.Tables[0];        gv.DataBind(  );     }     private DataSet GetDataSet(  )     {        DataSet dsData = new DataSet(  );        XmlDataDocument doc = new XmlDataDocument(  );        doc.DataSet.ReadXml(Server.MapPath("Northwind.xml"));        dsData = doc.DataSet;        return dsData;     }  public void RemovedCallback(string cacheKey,                                  Object cacheObject,                                  CacheItemRemovedReason reasonToRemove)    {       WriteFile("Cache removed for following reason: " +          reasonToRemove.ToString(  ));    }    private void WriteFile(string strText)    {       System.IO.StreamWriter writer = new System.IO.StreamWriter(                                                    @"C:\test.txt", true);       string str;       str = DateTime.Now.ToString(  ) + "  " + strText;       writer.WriteLine(str);       writer.Close(  );    }  protected void btnClear_Click(object sender, EventArgs e)     {       Cache.Remove("GridViewDataSet");       CreateGridView(  );     }    protected void btnInit_Click(object sender, EventArgs e)    {       //  Initialize caches to depend on.       Cache["Depend0"] = "This is the first dependency.";       Cache["Depend1"] = "This is the 2nd dependency.";       Cache["Depend2"] = "This is the 3rd dependency.";    }    protected void btnKey0_Click(object sender, EventArgs e)    {       Cache["Depend0"] = "This is a changed first dependency.";    } } 

Looking at the lines of code that call the Insert method, you can see that one more parameter has been added: onRemove (in addition to the three parameters for time and priority dependencies). This is the callback.

 Cache.Insert("GridViewDataSet", dsGrid, cacheDepends,                   DateTime.Now.AddSeconds(10),                   Cache.NoSlidingExpiration,                   CacheItemPriority.Default,  onRemove  ); 

The callback method is encapsulated within a delegate , which is a reference type that encapsulates a method with a specific signature and return type. The callback method is of the same type and must have the same signature as the CacheItemRemovedCallback delegate. The callback method is declared as a private member of the Page class with the following line of code:

 private static CacheItemRemovedCallback onRemove = null; 

Further down, in the CreateGridView method, the callback delegate is instantiated, passing in a reference to the appropriate method:

 onRemove = new CacheItemRemovedCallback(this.RemovedCallback); 

This instantiation associates the onRemove delegate with the RemovedCallback method, which is implemented further down.

 public void RemovedCallback(String cacheKey,                                 Object cacheObject,                                 CacheItemRemovedReason reasonToRemove)     {        WriteFile("Cache removed for following reason: " +           reasonToRemove.ToString(  ));     } 

This code has the required signature, which consists of three parameters:

  • A string containing the key of the cached item

  • An object that is the cached item

  • A member of the CacheItemRemovedReason enumeration

This last parameter, CacheItemRemovedReason , provides the reason that the cached item was removed from the cache. It can have one of the values shown in Table 17-5.

Table 17-5. Members of the CacheItemRemovedReason enumeration

Reason

Description

DependencyChanged

A file or item key dependency has changed.

Expired

The cached item has expired.

Removed

The cached item has been explicitly removed by the Remove method or replaced by another item with the same key.

Underused

The cached item was removed to free up system memory.


In this example, the only thing the RemovedCallback method does is call WriteFile to make a log entry. It does this by instantiating a StreamWriter on the log file:

 System.IO.StreamWriter writer = new System.IO.StreamWriter(                                                  @"C:\test.txt",true); 

The second parameter for the StreamWriter class, the Boolean, specifies to append to the file if it exists and to create the file if it doesn't exist. If false , it would have overwritten the file if it existed. For this to work as written, the account used by the ASP.NET process must have sufficient rights to create files in the root directory.

The WriteLine method is then used to write the string to be logged to the log file.



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

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