Listing 23-3: Using HTTP Headers to force the browser not to cache on the client-side VB Protected Sub Page_LoadByVal sender As Object, _ ByVal e As System.EventArgs Handles Me.Load Resp
Trang 1TheHttpCachePolicyclass gives you an object model for managing client-side state that insulates
you from adding HTTP headers yourself Add the lines from Listing 23-3 to yourPage_Loadto influence the Response’s headers and the caching behavior of the browser This listing tells the browser not to
cache this Response in memory nor store it on disk It also directs the Response to expire
immediately
Listing 23-3: Using HTTP Headers to force the browser not to cache on the client-side
VB
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
Response.Cache.SetCacheability(HttpCacheability.NoCache)
Response.Cache.SetNoStore()
Response.Cache.SetExpires(DateTime.MinValue)
Response.Write(DateTime.Now.ToLongTimeString())
End Sub
C#
protected void Page_Load(object sender, EventArgs e)
{
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore();
Response.Cache.SetExpires(DateTime.MinValue);
Response.Write(DateTime.Now.ToLongTimeString());
}
Figure 23-3
Trang 2Compare the results of running Listing 23-3 in the before Figure 23-2 and then in the after Figure 23-3 Two
new HTTP headers have been injected directing the client’s browser and theCache-ControlHeader has
changed tono-cache, no-store The Output Caching HttpModule will respect these HTTP headers,
so sendingno-cache,no storeto the browser also advises the HttpModule to record the response as a
cache miss
If your ASP.NET application contains a considerable number of relatively static or non–time-sensitive
pages, consider what your client-side caching strategy is It’s better to take advantage of the disk space
and the memory of your users’ powerful client machines rather than burdening your server’s limited
resources
Caching Programmatically
Output Caching is a very declarative business UserControls and Pages can be marked up with
Output-Cachedirectives and dramatically change the behavior of your site Declarative caching controls the life
cycle of HTML markup, but ASP.NET also includes deep imperative programmatic support for caching
objects
Data Caching Using the Cache Object
Another method of caching is to use theCacheobject to start caching specific data items for later use
on a particular page or group of pages TheCacheobject enables you to store everything from simple
name/value pairs to more complex objects such as datasets and entire.aspxpages
Although this is quite similar to Session state, the Cache object is shared by all users
of the particular web server’s app domain that is hosting this application So if you
put a particular item in the Cache , all users will be able to see that object This may
not work as expected in a server farm scenario since you can’t be assured of which
server the user will hit next, and even if there’s only one server involved there may
be more than one app domain running this application Also, the server is free to
invalidate any cached item at any time if it needs to reclaim some of the memory.
You use theCacheobject in the following fashion:
VB
Cache("WhatINeedToStore") = myDataSet
C#
Cache["WhatINeedToStore"] = myDataSet;
After an item is in the cache, you can retrieve it later as shown here:
VB
Dim ds As New DataSet
ds = CType(Cache("WhatINeedToStore"), DataSet)
C#
DataSet ds = new DataSet();
ds = (DataSet)Cache["WhatINeedToStore"];
Trang 3Using theCacheobject is an outstanding way to cache your pages and is, in fact, what theOutputCache directive uses under the covers This small fragment shows the simplest use of theCacheobject Simply put an object reference in it However, the real power of theCacheobject comes with its capability to
invalidate itself That’s where cache dependencies come in
You must always follow the pattern of testing to see if an item is in the Cache, and
if not you need to do whatever processing is necessary to re-create the object Once
re-created you can insert it back into the Cache to become available for the next
request.
Controlling the ASP.NET Cache
Ordinarily the default parameters set by ASP.NET for the caching subsystem are appropriate for general use They are configurable, however, within the machine.config or web.config These options let you
make changes like preventing cached items from expiring when the system is under memory pressure,
or turning off item expiration completely You can set the maximize size the application’s private bytes before the cache begins to flush items
<system.web>
<cache disableMemoryCollection="false"
disableExpiration="false" privateBytesLimit="0"
percentagePhysicalMemoryUsedLimit="90"
privateBytesPollTime="00:02:00" />
snip
I encourage you to leave the default values as they are unless you’ve done formal profiling of your
application and understand how it utilizes the Cache More detail on this section can be found on MSDN here:http://msdn2.microsoft.com/en-us/library/ms228248.aspx
Cache Dependencies
Using theCacheobject, you can store and also invalidate items in the cache based on several different
dependencies In ASP.NET 1.0/1.1, the only possible dependencies were the following:
❑ File-based dependencies
When inserting items into the cache using theCacheobject, you set the dependencies with theInsert
method, as shown in the following example:
Cache.Insert("DSN", connectionString, _
New CacheDependency(Server.MapPath("myconfig.xml")))
By using a dependency when the item being referenced changes, you remove the cache for that item from
memory
Trang 4Cache Dependencies were improved in ASP.NET 2.0 with the addition of the
AggregateCacheDepen-dencyclass, the newly extendableCacheDependencyclass, and the capability to create your own custom
CacheDependencyclasses These three things are discussed in the following sections
The AggregateCacheDependency Class
TheAggregateCacheDependencyclass is like theCacheDependencyclass but it enables you to create
an association connecting an item in the cache with many disparate dependencies of different types.
For example, if you have a cached data item that is built from XML from a file and you also have
information from a SQL database table, you can create anAggregateCacheDependencywith inserted
CacheDependencyobjects for each subdependency To do this, you callCache.Insertand add the
AggregateCacheDependencyinstance For example:
Dim agg as new AggregateCacheDependency()
agg.Insert(New CacheDependency(Server.MapPath("myconfig.xml")))
agg.Insert(New SqlCacheDependency("Northwind", "Customers"))
Cache.Insert("DSN", connectionString, agg)
Note thatAggregateCacheDependencyis meant to be used with different kinds ofCacheDependency
classes If you simply want to associate one cached item with multiple files, use an overload of
CacheDe-pendency, as in this example:
VB
Cache.Insert("DSN", yourObject, _
New System.Web.Caching.CacheDependency( _
New String() _ { _
Server.MapPath("foo.xml"), _ Server.MapPath("bar.xml") _ } _
) _
)
C#
Cache.Insert("DSN", yourObject,
new System.Web.Caching.CacheDependency(
new string[]
{ Server.MapPath("foo.xml"), Server.MapPath("bar.xml") }
)
);
TheAggregateCacheDependencyclass is made possible by the new support for extending the previously
sealedCacheDependencyclass You can use this innovation to create your own customCacheDependency
The Unsealed CacheDependency Class
A big change in caching in ASP.NET 2.0 was that theCacheDependencyclass has been refactored and
unsealed (or made overrideable) This allows us tocreate classes that inherit from theCacheDependency
Trang 5class and create more elaborate dependencies that are not limited to theTime,Key, orFile
dependencies
When you create your own cache dependencies, you have the option to add procedures for such things
as Web services data, only-at-midnight dependencies, or textual string changes within a file The depen-dencies you create are limited only by your imagination The unsealing of theCacheDependencyclass
puts you in the driver’s seat to let you decide when items in theCacheneed to be invalidated
Along with the unsealing of theCacheDependencyclass, the ASP.NET team has also built a SQL Server cache dependency —SqlCacheDependency A SQL cache dependency was the caching feature most
requested by ASP.NET 1.0/1.1 developers When a cache becomes invalid because a table changes within the underlying SQL Server, you now know it immediately
BecauseCacheDependencyis now unsealed, you can derive your own custom Cache Dependencies; that’s what you do in the next section
Creating Custom Cache Dependencies
ASP.NET has time-based, file-based, and SQL-basedCacheDependencysupport You might ask yourself why you would write your ownCacheDependency Here are a few ideas:
❑ Invalidate the cache from the results of an Active Directory lookup query
❑ Invalidate the cache upon arrival of an MSMQ or MQSeries message
❑ Create an Oracle-specificCacheDependency
❑ Invalidate the cache using data reported from an XML Web service
❑ Update the cache with new data from a Stock Price service
The new version of theCacheDependencyclass, while introducing no breaking changes to existing
ASP.NET 1.1 code, exposes three new members and a constructor overload that developers can use:
❑ GetUniqueID: When overridden, enables you to return a unique identifier for a custom cache
dependency to the caller
❑ DependencyDispose: Used for disposing of resources used by the custom cache dependency
class When you create a custom cache dependency, you are required to implement this method
❑ NotifyDependencyChanged: Called to cause expiration of the cache item dependent on the
cus-tom cache dependency instance
❑ New Public Constructor
Listing 23-4 creates a newRssCacheDependencythat invalidates a cache key if an RSS (Rich Site
Summary) XML Document has changed
Listing 23-4: Creating an RssCacheDependency class
VB
Imports System
Imports System.Web
Trang 6Imports System.Threading
Imports System.Web.Caching
Imports System.Xml
Public Class RssCacheDependency
Inherits CacheDependency
Dim backgroundThread As Timer
Dim howOften As Integer = 900
Dim RSS As XmlDocument
Dim RSSUrl As String
Public Sub New(ByVal URL As String, ByVal polling As Integer)
howOften = polling RSSUrl = URL RSS = RetrieveRSS(RSSUrl)
If backgroundThread Is Nothing Then backgroundThread = New Timer( _ New TimerCallback(AddressOf CheckDependencyCallback), _
Me, (howOften * 1000), (howOften * 1000)) End If
End Sub
Function RetrieveRSS(ByVal URL As String) As XmlDocument
Dim retVal As New XmlDocument retVal.Load(URL)
Return retVal End Function
Public Sub CheckDependencyCallback(ByVal Sender As Object)
Dim CacheDepends As RssCacheDependency = _ CType(Sender, RssCacheDependency) Dim NewRSS As XmlDocument = RetrieveRSS(RSSUrl)
If Not NewRSS.OuterXml = RSS.OuterXml Then CacheDepends.NotifyDependencyChanged(CacheDepends, EventArgs.Empty) End If
End Sub
Protected Overrides Sub DependencyDispose()
backgroundThread = Nothing MyBase.DependencyDispose() End Sub
Public ReadOnly Property Document() As XmlDocument
Get Return RSS End Get End Property
End Class
C#
using System;
using System.Web;
using System.Threading;
using System.Web.Caching;
using System.Xml;
Trang 7public class RssCacheDependency : CacheDependency
{
Timer backgroundThread;
int howOften = 900;
XmlDocument RSS;
string RSSUrl;
public RssCacheDependency(string URL, int polling)
{
howOften = polling;
RSSUrl = URL;
RSS = RetrieveRSS(RSSUrl);
if (backgroundThread == null)
{
backgroundThread = new Timer(
new TimerCallback(CheckDependencyCallback), this, (howOften * 1000), (howOften * 1000));
}
}
public XmlDocument RetrieveRSS(string URL)
{
XmlDocument retVal = new XmlDocument();
retVal.Load(URL);
return retVal;
}
public void CheckDependencyCallback(object sender)
{
RssCacheDependency CacheDepends = sender as RssCacheDependency;
XmlDocument NewRSS = RetrieveRSS(RSSUrl);
if (NewRSS.OuterXml != RSS.OuterXml)
{
CacheDepends.NotifyDependencyChanged(CacheDepends, EventArgs.Empty);
}
}
override protected void DependencyDispose()
{
backgroundThread = null;
base.DependencyDispose();
}
public XmlDocument Document
{
get
{
return RSS;
}
}
}
Create a new Web site and put theRssCacheDependencyclass in a/Codefolder Create adefault.aspx and drag two text boxes, a label, and a button onto the HTML Design view Execute the Web site and
enter an RSS URL for a blog (like mine atwww.hanselman.com/blog/SyndicationService.asmx/
GetRss), and click the button The program checks theCacheobject using the URL itself as a key If the
Trang 8XmlDocumentcontaining RSS doesn’t exist in the cache, a newRssCacheDependencyis created with a
10-minute (600-second) timeout TheXmlDocumentis then cached, and all future requests within the next
10 minutes to this page retrieve the RSSXmlDocumentfrom the cache
Next, your newRssCacheDependencyclass from Listing 23-4 is illustrated in the following fragment The
RssCacheDependencyis created and passed into the call toCache.Insert TheCacheobject handles the
lifetime and calling of the methods of theRssCacheDependencyinstance:
VB
<%@ Page Language="VB" ValidateRequest="false" %>
<html>
<head runat="server">
<title>Custom Cache Dependency Example</title>
</head>
<body>
<form runat="server"> RSS URL:
<asp:TextBox ID="TextBox1" Runat="server"/>
<asp:Button ID="Button1" onclick="Button1_Click" Runat="server"
Text="Get RSS" />
Cached:<asp:Label ID="Label2" Runat="server"></asp:Label><br />
RSS:<br />
<asp:TextBox ID="TextBox2" Runat="server" TextMode="MultiLine"
Width="800px" Height="300px"></asp:TextBox>
</form>
</body>
</html>
<script runat="server">
Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim RSSUrl As String = TextBox1.Text Label2.Text = "Loaded From Cache"
If Cache(TextBox1.Text) Is Nothing Then Label2.Text = "Loaded Fresh"
Dim itDepends As New RssCacheDependency(RSSUrl, 600) Cache.Insert(RSSUrl, itDepends.Document, itDepends) End If
TextBox2.Text = CType(Cache(TextBox1.Text), _ System.Xml.XmlDocument).OuterXml
End Sub
</script>
C#
<%@ Page Language="C#" ValidateRequest="false" %>
<script runat="server">
void Button1_Click(object sender, System.EventArgs e)
{
string RSSUrl = TextBox1.Text;
Label2.Text = "Loaded From Cache";
if (Cache[TextBox1.Text] == null) {
Label2.Text = "Loaded Fresh";
RssCacheDependency itDepends = new RssCacheDependency(RSSUrl, 600);
Cache.Insert(RSSUrl, itDepends.Document, itDepends);
}
Trang 9TextBox2.Text = ((System.Xml.XmlDocument)Cache[TextBox1.Text]).OuterXml;
}
</script>
TheRssCacheDependencyclass creates aTimerbackground thread to poll for changes in the RSS feed If
it detects changes, theRssCacheDependencynotifies the caching subsystem with the NotifyDependency-Changedevent The cached value with that key clears, and the next page view forces a reload of the
requested RSS from the specified feed
Using the SQL Ser ver Cache Dependency
To utilize the SQL Server Cache Dependency feature in ASP.NET, you must perform a one-time setup
of your SQL Server database To set up your SQL Server, use theaspnet_regsql.exetool found at
C:\Windows\MicroSoft.NET\Framework\v2.0.50727/ This tool makes the necessary modifications to
SQL Server so that you can start working with the new SQL cache invalidation features
Follow these steps when using the new SQL Server Cache Dependency features:
1. Enable your database for SQL Cache Dependency support
2. Enable a table or tables for SQL Cache Dependency support
3. Include SQL connection string details in the ASP.NET application’sweb.config
4. Utilize the SQL Cache Dependency features in one of the following ways:
❑ Programmatically create aSqlCacheDependencyobject in code
❑ Add aSqlDependencyattribute to anOutputCachedirective
❑ Add aSqlCacheDependencyinstance to the Response object via Response.AddCache-Dependency
This section explains all the steps required and the operations available to you
To start, you need to get at theaspnet_regsql.exetool Open up the Visual Studio Command Prompt
by choosing Start ➪ All Programs➪Microsoft Visual Studio 2008 ➪ Visual Studio Tools ➪ Visual Studio
Command Prompt from the Windows Start menu After the prompt launches, type this command:
aspnet_regsql.exe -?
This code outputs the help command list for this command-line tool, as shown in the following:
SQL CACHE DEPENDENCY OPTIONS
-d <database> Database name for use with SQL cache dependency The
database can optionally be specified using the connection string with the -c option instead
(Required)
Trang 10-dd Disable a database for SQL cache dependency.
option
option
-t <table> Name of the table to enable or disable for SQL cache
dependency Requires -et or -dt option
The following sections show you how to use some of these commands
Enabling Databases for SQL Server Cache Invalidation
To use SQL Server cache invalidation with SQL Server 7 or 2000, begin with two steps The first step
enables the appropriate database In the second step, you enable the tables that you want to work with
You must perform both steps for this process to work If you want to enable your databases for SQL
cache invalidation and you are working on the computer where the SQL Server instance is located, you
can use the following construct If your SQL instance is on another computer, changelocalhostin this
example to the name of the remote machine
aspnet_regsql.exe -S localhost -U sa -P password -d Northwind –ed
This produces something similar to the following output:
Enabling the database for SQL cache dependency
Finished
From this command prompt, you can see that we simply enabled the Northwind database (the sample
database that comes with SQL Server) for SQL cache invalidation The name of the SQL machine was
passed in with-S, the username with-U, the database with-d, and most importantly, the command to
enable SQL cache invalidation was-ed
Now that you have enabled the database for SQL cache invalidation, you can enable one or more tables
contained within the Northwind database
Enabling Tables for SQL Server Cache Invalidation
You enable more tables by using the following command:
aspnet_regsql.exe -S localhost -U sa -P password -d Northwind -t Customers –et
aspnet_regsql.exe -S localhost -U sa -P password -d Northwind -t Products –et
You can see that this command is not much different from the one for enabling the database, except for
the extra-t Customersentry and the use of-etto enable the table rather than-edto enable a database
Customersis the name of the table that is enabled in this case