All the examples in this chapter so far 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 almost any type: a data type, a web control, a class, a data set, etc.
Unlike output caching, which stores its data on a hard drive somewhere, the object cache is stored in server memory. As such, it is a limited resource and the careful developer will husband that resource carefully. That said, it is an easy way to buy significant performance benefits when used wisely.
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 descriptive data. Instead of your control requerying the database each time the data is requested, the data set is cached, so that subsequent requests for the data will be satisfied from high speed cache rather than the 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 domain 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 Hashtable object. The object being stored is the value, and the key is a descriptive string.
To clarify object caching, look at the code shown in Example 18-12. The web page in this listing will display a data grid containing data from the Bugs database. It will initially query data from the Bugs database, then store it in cache for subsequent requests. Example 18-12 contains the VB.NET source, while Example 18-13 shows the C# source. Example 18-13 omits the HTML, since it is identical to the VB.NET version shown in Example 18-12.
Example 18-12. Object caching a data set in VB.NET, vbObjCache-01.aspx
<%@ Page Language="vb" %><%@ Import namespace="System.Data" %>
<%@ Import namespace="System.Data.SqlClient" %>
<script runat="server"> sub Page_Load(ByVal Sender as Object, _ ByVal e as EventArgs)if not IsPostBack then
CreateDataGrid( )
end if
end sub sub btn_OnClick(ByVal Sender as Object, _ ByVal e as EventArgs)Cache.Remove("DataGridDataSet")
CreateDataGrid( )
end subsub CreateDataGrid( )
dim dsGrid as DataSet
dsGrid = CType(Cache("DataGridDataSet"), DataSet)
if dsGrid is Nothing then
dsGrid = GetDataSet( )
Cache("DataGridDataSet") = dsGrid
lbl.Text = "Data from database."
else
lbl.Text = "Data from cache."
end if
dg.DataSource=dsGrid.Tables(0)
dg.DataBind( )
end sub
public function GetDataSet( ) as DataSet ' connect to the Bugs database dim connectionString as string = "server=MyServer; uid=sa; " & _ "pwd=dan; database=Bugs" ' get records from the Bugs table dim commandString as string = "Select BugID, Description from Bugs" ' create the data set command object and the DataSet dim da as SqlDataAdapter = new SqlDataAdapter(commandString, _ connectionString) dim dsData as DataSet = new DataSet( ) ' fill the data set object da.Fill(dsData,"Bugs") return dsData end function </script> <html> <body> <form runat="server"> <h1>Object Caching</h1> <asp:Label id="lbl" runat="server"/> <br/> <br/> <asp:DataGrid id="dg" runat="server"/> <br/> <asp:Button id="btn" Text="Clear Cache" OnClick="btn_OnClick" runat="server"/> </form> </body> </html>
Example 18-13. Object Caching a DataSet in C#, csObjCache-01.aspx
<%@ Page Language="C#" %><%@ Import namespace="System.Data" %>
<%@ Import namespace="System.Data.SqlClient" %>
<script runat="server"> void Page_Load(Object Source, EventArgs E) {if (! IsPostBack)
{
CreateDataGrid( );
}
} void btn_OnClick(Object Source, EventArgs E) {Cache.Remove("DataGridDataSet");
CreateDataGrid( );
}public void CreateDataGrid( )
{
DataSet dsGrid;
dsGrid = (DataSet)Cache["DataGridDataSet"];
if (dsGrid == null)
{
dsGrid = GetDataSet( );
Cache["DataGridDataSet"] = dsGrid;
lbl.Text = "Data from database.";
}
else
{
lbl.Text = "Data from cache.";
}
dg.DataSource=dsGrid.Tables[0];
dg.DataBind( );
}
public DataSet GetDataSet( ) { // connect to the Bugs database string connectionString = "server=MyServer; uid=sa; pwd=dan; " + "database=Bugs"; // get records from the Bugs table string commandString = "Select BugID, Description from Bugs"; // 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,"Bugs"); return dsData; } </script>
The heart of Example 18-12 and Example 18-13 involves data access. For a complete
discussion of data access in ASP.NET, see Chapter 11 and Chapter 12. For now,
notice that the directives at the top of the code listing include two
Import
directives in order to make the classes and
methods from the System.Data and System.Data.SqlClient namespaces
available to the code.
While looking at the page directives, also notice that there is no
OutputCache
directive, since this example does not
use output caching.
A method named CreateDataGrid is called every time the data grid needs to be created. Notice that it is called in the Page_Load method the first time the page is loaded, but not on postback.
Looking at the CreateDataGrid method, a DataSet object is instantiated to contain the data that will be bound and displayed by the data grid. In VB.NET:
dim dsGrid as DataSet
and in C#:
DataSet dsGrid;
The Cache object with the key DataGridDataSet
is
then retrieved and assigned to the dsGrid
DataSetobject. In VB.NET:
dsGrid = CType(Cache("DataGridDataSet"), DataSet)
and in C#:
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, here DataSet. For this purpose, C# uses an
explicit cast, while VB.NET uses the
CType
function.
The dsGrid data set is then tested to see if it actually exists.
Although the DataSet object has been instantiated, it is only a
placeholder until it actually contains data. If the Cache object with
the key DataGridDataSet
has not yet been created
or has expired, then dsGrid still has no data in it. In VB.NET, this
is done using:
if dsGrid is Nothing then
and in C#, it’s:
if (dsGrid == null)
If the DataSet object does already contain data, meaning the Cache had been previously filled and was not expired, then the Label control’s Text property is 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. In VB.NET, the code is:
dsGrid = GetDataSet( )
Cache("DataGridDataSet") = dsGrid
lbl.Text = "Data from database."
and in C#, it’s:
dsGrid = GetDataSet( );
Cache["DataGridDataSet"] = dsGrid;
lbl.Text = "Data from database.";
In either case, once the data set is filled, the DataSource property of the DataGrid control on the web page is set to be the data set, and the DataGrid control is data bound. In VB.NET, the code is:
dg.DataSource=dsGrid.Tables(0)
dg.DataBind( )
and in C#, it’s:
dg.DataSource=dsGrid.Tables[0];
dg.DataBind( );
The result of running the code in Example 18-12 and Example 18-13 is shown in Figure 18-5.
The first time the web page is run, the label just above the DataGrid control will indicate that the data is coming directly from the database. Every subsequent time the form is requested, the label will change to say “Data from cache.”
There is no way for the cache in this example to expire (i.e., to go away). 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 btn, 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. In VB.NET, the code is:
Cache.Remove("DataGridDataSet")
and in C#, it’s:
Cache.Remove("DataGridDataSet");
In Example 18-12 and Example 18-13, the button event handler then refills the cache by calling the CreateDataGrid method. As an exercise in observing different behavior, comment out the line that calls CreateDataGrid in the btn_OnClick event procedure and observe the different behavior when you repost the page after clicking the Clear Cache button. When the line calling the CreateDataGrid method is not commented out, then 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.
Example 18-12 and Example 18-13 demonstrate 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, while the Insert method allows you to make some of the parameters optional, using default values for those parameters.
The syntax for the Add method in VB.NET is:
Cache.Add(KeyName
,KeyValue
,Dependencies
,AbsoluteExpiration
, _SlidingExpiration
,Priority
,CacheItemRemovedCallback
)
and for C#, it’s:
Cache.Add(KeyName
,KeyValue
,Dependencies
,AbsoluteExpiration
,SlidingExpiration
,Priority
,CacheItemRemovedCallback
);
In these syntaxes, KeyName
is a string
with the name of the key in the Cache dictionary, and
KeyValue
is the value to be inserted into
the Cache. KeyValue
is an object of any
type. All the other parameters will be described below.
While the Add method requires that all the parameters be provided, the Insert method is overloaded to allow several of the parameters to be optional.
An object may overload its methods, which means it may declare two or more methods with the same name. The compiler differentiates among these methods based on the number and type of parameters provided.
The syntax for the overloaded Insert methods in VB.NET is described in this list. For C#, the syntax is identical except that there is a terminating semicolon for each statement:
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 Example 18-12 or Example 18-13. Find the line in the CreateDataGrid method that looks like this in VB.NET:
Cache("DataGridDataSet") = dsGrid
or like this in C#:
Cache["DataGridDataSet"] = dsGrid;
Replace it with the following line in VB.NET:
Cache.Insert("DataGridDataSet", dsGrid)
or in C#:
Cache.Insert("DataGridDataSet", 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.
There is much more you can do with these methods.
One very useful feature exposed by the Cache class is dependencies. A dependency is a relationship between a cached item and either a point in time or an external object. If the designated point in time is reached or if the external object changes, then the cached item will be automatically expired and 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.
With a file change dependency, a cached item will become expired and be removed from the cache if a specified file has changed. 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 the XML file:
Use Start/Programs/Microsoft SQL ServerConfigure SQL XML Support in IIS.
Set a virtual directory -- BugsDB
Use the following URL in a browser:
http://localhost/bugsdb?sql=select+*+from+bugs+for+xml+auto&root=ROOT
Example 18-14 shows the contents of an XML file that
contains all the records from the Bugs table in the Bugs database.
The code in Example 18-14 can be modified to
demonstrate a file change dependency. Since the data set will be
coming from XML rather than SQL Server, replace the
Import
directive pointing to System.Data.SqlClient
with a directive pointing to System.Xml.
Example 18-14. Bugs.xml
<?xml version="1.0" encoding="utf-8" ?> <ROOT> <bugs BugID="1" Product="2" Version="0.1" Description="Update bug test" Reporter="3" /> <bugs BugID="2" Product="1" Version="0.1" Description="Does not report correct owner of bug" Reporter="5" /> <bugs BugID="3" Product="1" Version="0.1" Description="Does not show history of previous action" Reporter="6" /> <bugs BugID="4" Product="1" Version="0.1" Description="Fails to reload properly" Reporter="5" /> <bugs BugID="5" Product="2" Version="0.7" Description="Loses data overnight" Reporter="5" /> <bugs BugID="6" Product="2" Version="0.7" Description="HTML is not shown properly" Reporter="6" /> <bugs BugID="31" Product="1" Version="0.3" Description="this is test 7" Reporter="1" /> <bugs BugID="32" Product="2" Version="0.1" Description="New bug test" Reporter="3" /> <bugs BugID="33" Product="2" Version="0.1" Description="Cache test 3" Reporter="3" /> <bugs BugID="34" Product="2" Version="0.1" Description="Object cache test 1" Reporter="3" /> <bugs BugID="35" Product="2" Version="0.1" Description="Obj Cache test 2" Reporter="4" /> </ROOT>
Next, modify the CreateDataGrid andGetDataSet methods as shown in Example 18-15 for VB.NET and Example 18-16 for C#, where the highlighted lines of code are different from the code in Example 18-12 and 18-13.
Example 18-15. Cache file dependency in VB.NET, vbObjCache-02.aspx
sub CreateDataGrid( ) dim dsGrid as DataSet dsGrid = CType(Cache("DataGridDataSet"), DataSet) if dsGrid is Nothing then dsGrid = GetDataSet( )dim fileDepends as new CacheDependency(Server.MapPath("Bugs.xml"))
Cache.Insert("DataGridDataSet", dsGrid, fileDepends)
lbl.Text = "Data from XML file."
else lbl.Text = "Data from cache." end if dg.DataSource=dsGrid.Tables(0) dg.DataBind( ) end sub public function GetDataSet( ) as DataSetdim dsData as new DataSet( )
dim doc as new XmlDataDocument( )
doc.DataSet.ReadXml(Server.MapPath("Bugs.xml"))
dsData = doc.DataSet
return dsData end function
Example 18-16. Cache file dependency in C#, csObjCache-02.aspx
public void CreateDataGrid( ) { DataSet dsGrid; dsGrid = (DataSet)Cache["DataGridDataSet"]; if (dsGrid == null) { dsGrid = GetDataSet( );CacheDependency fileDepends = new
CacheDependency(Server.MapPath("Bugs.xml"));
Cache.Insert("DataGridDataSet", dsGrid, fileDepends);
lbl.Text = "Data from XML file.";
} else { lbl.Text = "Data from cache."; } dg.DataSource=dsGrid.Tables[0]; dg.DataBind( ); } public DataSet GetDataSet( ) {DataSet dsData = new DataSet( );
XmlDataDocument doc = new XmlDataDocument( );
doc.DataSet.ReadXml(Server.MapPath("Bugs.xml"));
dsData = doc.DataSet;
return dsData; }
The goal of the GetDataSet method is still to
return a data set. However, the source of the data for the data set
is now the XML file called Bugs.xml
. Since
ASP.NET stores data sets internally as XML, it is very easy to move
back and forth between XML and data sets. The XML object equivalent
to a data set 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 CreateDataGrid method, only three lines have changed from Example 18-12 and Example 18-13. 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 Example 18-12 and Example 18-13 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 the fact that the data is now coming from an XML file rather than a database.
Test this page by running the code from Example 18-15 or Example 18-16 in a browser. You will get something similar to Figure 18-6.
If you repost the page by highlighting the URL and pressing Enter, the label at the top of the page will indicate that the data is coming from the cache.
Now open the Bugs.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 wish to condition the cache dependency on an array of files or directories, the syntax for the CacheDependency constructor in Example 18-15 and Example 18-16 would take an array of file paths or directories rather than a single filename. So, for example, the single line of code in Example 18-15 and Example 18-16 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. In VB.NET, it would look something like:
dim fileDependsArray as string( ) = {Server.MapPath("Bugs.xml"), _ Server.MapPath("People.xml")} dim fileDepends as new CacheDependency(fileDependsArray)
string[] fileDependsArray = {Server.MapPath("Bugs.xml"), Server.MapPath("People.xml")}; CacheDependency fileDepends = new CacheDependency(fileDependsArray);
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.
In order 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 then passed in to the CacheDependency
constructor, along with an array of file paths. (If you do not wish
to define a dependency on any files or paths, then the array of file
paths can be Nothing
in VB.NET or
null
in C#.)
This is demonstrated in Example 18-17 and Example 18-18. In the web page in this listing, two buttons have been 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 button is unchanged.
The lines of code in Example 18-17 and Example 18-18 that are new or changed from Example 18-15 and Example 18-16 are highlighted. Note that the HTML is not included in the C# listing in Example 18-18, since it is identical to that in the VB.NET listing.
Example 18-17. Cache item dependency in VB.NET, vbObjCache-03.aspx
<%@ Page Language="vb" %> <%@ Import namespace="System.Data" %> <%@ Import namespace="System.Xml" %> <script runat="server"> sub Page_Load(ByVal Sender as Object, _ ByVal e as EventArgs) if not IsPostBack then CreateDataGrid( ) end if end sub sub btnClear_OnClick(ByVal Sender as Object, _ ByVal e as EventArgs) Cache.Remove("DataGridDataSet") CreateDataGrid( ) end subsub btnInit_OnClick(ByVal Sender as Object, _
ByVal e as EventArgs)
' 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."
end sub
sub btnKey0_OnClick(ByVal Sender as Object, _
ByVal e as EventArgs)
Cache("Depend0") = "This is a changed first dependency."
end sub
sub CreateDataGrid( ) dim dsGrid as DataSet dsGrid = CType(Cache("DataGridDataSet"), DataSet) if dsGrid is Nothing then dsGrid = GetDataSet( )dim fileDependsArray as string( ) = {Server.MapPath("Bugs.xml")}
dim cacheDependsArray as string( ) = _
{"Depend0","Depend1", "Depend2"}
dim cacheDepends as new CacheDependency( _
fileDependsArray, cacheDependsArray)
Cache.Insert("DataGridDataSet", dsGrid, cacheDepends)
lbl.Text = "Data from XML file." else lbl.Text = "Data from cache." end if dg.DataSource=dsGrid.Tables(0) dg.DataBind( ) end sub public function GetDataSet( ) as DataSet dim dsData as new DataSet( ) dim doc as new XmlDataDocument( ) doc.DataSet.ReadXml(Server.MapPath("Bugs.xml")) dsData = doc.DataSet return dsData end function </script> <html> <body> <form runat="server"> <h1>Object Caching</h1> <h2>Cache Item Dependency</h2> <asp:Label id="lbl" runat="server"/><br/>
<br/>
<asp:DataGrid
id="dg"
runat="server"/>
<br/>
<asp:Button
id="btnClear"
Text="Clear Cache"
OnClick="btnClear_OnClick"
runat="server"/>
<br/>
<br/>
<asp:Button
id="btnInit"
Text="Initialize Keys"
OnClick="btnInit_OnClick"
runat="server"/>
<asp:Button
id="btnKey0"
Text="Change Key 0"
OnClick="btnKey0_OnClick"
runat="server"/>
</form> </body> </html>
Example 18-18. Cache item dependency in C#, csObjCache-03.aspx
<%@ Page Language="C#" %> <%@ Import namespace="System.Data" %> <%@ Import namespace="System.Xml" %> <script runat="server"> void Page_Load(Object Source, EventArgs E) { if (! IsPostBack) { CreateDataGrid( ); } }void btnClear_OnClick(Object Source, EventArgs E)
{
Cache.Remove("DataGridDataSet");
CreateDataGrid( );
}
void btnInit_OnClick(Object Source, 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.";
}
void btnKey0_OnClick(Object Source, EventArgs E)
{
Cache["Depend0"] = "This is a changed first dependency.";
}
public void CreateDataGrid( ) { DataSet dsGrid; dsGrid = (DataSet)Cache["DataGridDataSet"]; if (dsGrid == null) { dsGrid = GetDataSet( );string[] fileDependsArray = {Server.MapPath("Bugs.xml")};
string[] cacheDependsArray = {"Depend0","Depend1", "Depend2"};
CacheDependency cacheDepends = new CacheDependency
(fileDependsArray, cacheDependsArray);
Cache.Insert("DataGridDataSet", dsGrid, cacheDepends);
lbl.Text = "Data from XML file."; } else { lbl.Text = "Data from cache."; } dg.DataSource=dsGrid.Tables[0]; dg.DataBind( ); } public DataSet GetDataSet( ) { DataSet dsData = new DataSet( ); XmlDataDocument doc = new XmlDataDocument( ); doc.DataSet.ReadXml(Server.MapPath("Bugs.xml")); dsData = doc.DataSet; return dsData; } </script>
In the btnInit_OnClick event handler, the controlling cache items are created. The values of the cached items are not important for this example, except as something to change when the Change Key 0 button is clicked, as is done in the event handler for that button, btnKey0_OnClick.
The real action here occurs in the CreateDataGrid 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 Nothing
or null
for VB.NET or C#, respectively. For example, in VB.NET, the code
would be:
dim cacheDepends as new CacheDependency(Nothing, cacheDependsArray)
and in C#, it would be:
CacheDependency cacheDepends = new CacheDependency(null, cacheDependsArray);
Running the code in Example 18-17 or Example 18-18 brings up the page shown in Figure 18-7. Initially, the label above the data grid will
show that the data is from the XML file. Re-entering the URL will
cause the data to come from the Cache. Clicking any of the buttons or
changing the contents of Bugs.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. Although 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.
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.
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 (the same in both VB.NET and C#, except for the closing semicolon):
Cache.Insert(KeyName
,KeyValue
,Dependencies
,AbsoluteExpiration
,SlidingExpiration
)
If you don’t want any file or cached item
dependencies, then the Dependencies
parameter should be Nothing
in VB.NET or
null
in C#. 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, 2001 at 1:23:45
P.M. The code to implement that type of absolute expiration would
look something like the following (in C#):
DateTime expDate = new DateTime(2001,8,21,13,23,45); Cache.Insert("DataGridDataSet", dsGrid, null, expDate, TimeSpan.Zero);
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 (again in C# -- VB.NET is identical except for the trailing semicolon and possibly a line continuation character):
Cache.Insert("DataGridDataSet", dsGrid, null, DateTime.Now.AddMinutes(30), TimeSpan.Zero);
This line of code inserts the specified data set into the Cache, then expires that item 30 minutes after it was inserted. This scenario would be useful when accessing a slowly changing database where it was only necessary to be sure that the data presented was no more than 30 minutes old.
Suppose that the data was extremely volatile and/or needed to be very current. Then 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), TimeSpan.Zero);
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. To implement this
scenario, use the following line of code (again in C#, with the
VB.NET version nearly identical):
Cache.Insert("DataGridDataSet", dsGrid, null, DateTime.MaxValue, TimeSpan.FromSeconds(30));
DateTime.MaxValue is used for the
AbsoluteExpiration
parameter. This
constant is the largest possible value of DateTime, corresponding to
11:59:59 PM, 12/31/9999. (That’s a millennium
problem we can live with.) This guarantees that the cached item
won’t expire due to the absolute time being
exceeded.
SlidingExpiration
can be disabled
by
setting
its
value
to
CacheNoSlidingExpiration
.
One of the features of object caching is scavenging, where ASP.NET automatically removes seldom used items from the Cache object if server memory becomes scarce. This frees up memory to handle a higher volume of page requests.
Scavenging is controlled through thePriority
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 through method overloading.
The Priority
parameter indicates the cost of the
cached item relative to the other items stored in the cache. This
parameter is used by the cache when it evicts objects in order 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, shown in Table 18-2 in descending order of priority.
Table 18-2. Members of the CacheItemPriority enumeration
Priorityvalue |
Description |
---|---|
Items with this priority will not be evicted | |
Items with this priority level are the least likely to be evicted | |
Items with this priority level are less likely to be evicted than
items assigned | |
This is equivalent to | |
The default value | |
Items with this priority level are more likely to be evicted than
items assigned | |
Items with this priority level are the most likely to be evicted |
To implement scavenging, use the following line of code in VB.NET:
Cache.Insert("DataGridDataSet", dsGrid, null, DateTime.MaxValue, TimeSpan.Zero CacheItemPriority.High, Nothing)
and in C#:
Cache.Insert("DataGridDataSet", dsGrid, null, DateTime.MaxValue, TimeSpan.Zero CacheItemPriority.High, null);
The final parameter in the above lines of code pertain to callback support, which will be covered in the next section.
Since these Insert method calls use all seven parameters, you could also use the Add method with the same parameters.
It
may be useful to be informed when an item
is removed from the cache for any reason. Perhaps you will want to
reinsert the item into the cache, or perhaps you will want to know if
you need to install more memory in your web server. Such notification
is implemented using the
CacheItemRemovedCallback
parameter of the
Add or Insert methods. This parameter
specifies a callback method to be run when the
cached item is removed.
In the example web page shown in Example 18-19 (VB.NET) and Example 18-20 (C#), support is added for a callback when the cached item is expired or removed from the cache. This callback method, RemovedCallback, makes a log entry in a text file in the root of drive C. The log entry has a timestamp and the reason for the removal. The lines in Example 18-19 and Example 18-20 that are changed or new from the previous example are highlighted.
Example 18-19. Cache callbacks in VB.NET, vbObjCache-04.aspx
<%@ Page Language="vb" %> <%@ Import namespace="System.Data" %> <%@ Import namespace="System.Xml" %> <script runat="server">private shared onRemove as CacheItemRemovedCallback = Nothing
sub Page_Load(ByVal Sender as Object, _ ByVal e as EventArgs) if not IsPostBack then CreateDataGrid( ) end if end sub sub btnClear_OnClick(ByVal Sender as Object, _ ByVal e as EventArgs) Cache.Remove("DataGridDataSet") CreateDataGrid( ) end sub sub btnInit_OnClick(ByVal Sender as Object, _ ByVal e as EventArgs) ' 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." end sub sub btnKey0_OnClick(ByVal Sender as Object, _ ByVal e as EventArgs) Cache("Depend0") = "This is a changed first dependency." end sub sub CreateDataGrid( ) dim dsGrid as DataSet dsGrid = CType(Cache("DataGridDataSet"), DataSet)onRemove = new CacheItemRemovedCallback( _
AddressOf Me.RemovedCallback)
if dsGrid is Nothing then dsGrid = GetDataSet( ) dim fileDependsArray as string( ) = _ {Server.MapPath("Bugs.xml")} dim cacheDependsArray as string( ) = _ {"Depend0","Depend1", "Depend2"} dim cacheDepends as new CacheDependency( _ fileDependsArray, cacheDependsArray) Cache.Insert("DataGridDataSet", dsGrid, cacheDepends, _ DateTime.Now.AddSeconds(10), _ TimeSpan.Zero, _ CacheItemPriority.Default, _onRemove)
lbl.Text = "Data from XML file." else lbl.Text = "Data from cache." end if dg.DataSource=dsGrid.Tables(0) dg.DataBind( ) end subpublic sub RemovedCallback(k As String, _
v As Object, _
r As CacheItemRemovedReason)
Call WriteFile("Cache removed for following reason: " & _
r.ToString( ))
end sub
public sub WriteFile(strText as string)
dim writer as System.IO.StreamWriter = new System.IO.StreamWriter( _
"C:\test.txt",true)
dim str as string
str = DateTime.Now.ToString( ) & " " & strText
writer.WriteLine(str)
writer.Close( )
end sub
public function GetDataSet( ) as DataSet dim dsData as new DataSet( ) dim doc as new XmlDataDocument( ) doc.DataSet.ReadXml(Server.MapPath("Bugs.xml")) dsData = doc.DataSet return dsData end function </script> <html> <body> <form runat="server"> <h1>Object Caching</h1> <h2>Cache Callbacks</h2> <asp:Label id="lbl" runat="server"/> <br/> <br/> <asp:DataGrid id="dg" runat="server"/> <br/> <asp:Button id="btnClear" Text="Clear Cache" OnClick="btnClear_OnClick" runat="server"/> <br/> <br/> <asp:Button id="btnInit" Text="Initialize Keys" OnClick="btnInit_OnClick" runat="server"/> <asp:Button id="btnKey0" Text="Change Key 0" OnClick="btnKey0_OnClick" runat="server"/> </form> </body> </html>
Example 18-20. Cache callbacks in C#, csObjCache-04.aspx
<%@ Page Language="C#" %> <%@ Import namespace="System.Data" %> <%@ Import namespace="System.Xml" %> <script runat="server">private static CacheItemRemovedCallback onRemove = null;
void Page_Load(Object Source, EventArgs E) { if (! IsPostBack) { CreateDataGrid( ); } } void btnClear_OnClick(Object Source, EventArgs E) { Cache.Remove("DataGridDataSet"); CreateDataGrid( ); } void btnInit_OnClick(Object Source, 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."; } void btnKey0_OnClick(Object Source, EventArgs E) { Cache["Depend0"] = "This is a changed first dependency."; } public void CreateDataGrid( ) { DataSet dsGrid; dsGrid = (DataSet)Cache["DataGridDataSet"];onRemove = new CacheItemRemovedCallback(this.RemovedCallback);
if (dsGrid == null) { dsGrid = GetDataSet( ); string[] fileDependsArray = {Server.MapPath("Bugs.xml")}; string[] cacheDependsArray = {"Depend0","Depend1", "Depend2"}; CacheDependency cacheDepends = new CacheDependency( fileDependsArray, cacheDependsArray); Cache.Insert("DataGridDataSet", dsGrid, cacheDepends, DateTime.Now.AddSeconds(10), TimeSpan.Zero, CacheItemPriority.Default,onRemove
); lbl.Text = "Data from XML file."; } else { lbl.Text = "Data from cache."; } dg.DataSource=dsGrid.Tables[0]; dg.DataBind( ); }public void RemovedCallback(String k,
Object v,
CacheItemRemovedReason r)
{
WriteFile("Cache removed for following reason: " + r.ToString( ));
}
void WriteFile(string strText)
{
System.IO.StreamWriter writer = new System.IO.StreamWriter(
@"C: est.txt",true);
string str;
str = DateTime.Now.ToString( ) + " " + strText;
writer.WriteLine(str);
writer.Close( );
}
public DataSet GetDataSet( ) { DataSet dsData = new DataSet( ); XmlDataDocument doc = new XmlDataDocument( ); doc.DataSet.ReadXml(Server.MapPath("Bugs.xml")); dsData = doc.DataSet; return dsData; } </script>
Looking at the lines of code that call the Insert method,
you can see that one more parameter has been added,
onRemove
. This is the callback.
The callback method is encapsulated within a delegate . A delegate 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. In VB.NET, the line of code is:
private shared onRemove as CacheItemRemovedCallback = Nothing
and in C# it’s:
private static CacheItemRemovedCallback onRemove = null;
Further down, in the CreateDataGrid method, the callback delegate is instantiated, passing in a reference to the appropriate method. In VB.NET, the code is:
onRemove = new CacheItemRemovedCallback( _AddressOf Me.RemovedCallback)
and in C#, it’s:
onRemove = new CacheItemRemovedCallback(this.RemovedCallback);
This instantiation associates the onRemove delegate with the
RemovedCallback method. Notice the use of the
AddressOf
keyword in VB.NET to create a
reference to the method, which is not necessary in C#.
The RemovedCallBack method, reproduced here in both VB.NET:
public sub RemovedCallback(k As String, _
v As Object, _
r As CacheItemRemovedReason)
Call WriteFile("Cache removed for following reason: " & _
r.ToString( ))
end sub
and C#:
public void RemovedCallback(String k,
Object v,
CacheItemRemovedReason r)
{
WriteFile("Cache removed for following reason: " + r.ToString( ));
}
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 18-3.
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. In VB.NET, the code is:
dim writer as System.IO.StreamWriter = new System.IO.StreamWriter( _
"C:\test.txt",true)
and in C#, it’s:
System.IO.StreamWriter writer = new System.IO.StreamWriter(
@"C: est.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.
The WriteLine method is then used to write the string to be logged to the log file.
18.222.164.141