What is Caching?
Caching
is one of the least used and misunderstood, yet most powerful features of
ASP.NET -- both in the 1.1 flavor, and even more so in ASP.NET 2.0. Most
developers do not get into the details of harnessing the power of caching until
they get to at least the intermediate proficiency level, and some never use it
at all. Caching works a number of different ways in the 2.0 model, and offers
finer - grained control over features and behavior. The purpose of this article
is to provide you with a basic understanding of some of these features and how
they work in a single article that should help most beginners to "Get to
First Base" easily, with a minimum amount of time invested.
At a minimum a developer wants to be able to cache some (or
possibly all) of the pages in her ASP.NET Application. The simplest way to
achieve this is to add the @ OutputCache directive to the top of the .aspx file
of each page:
<%@ OutputCache Duration="5"
VaryByParam="none" %>
Now, that was easy, wasn't it? But -
exactly what does it do? You are specifying how long the page is to be retained
in the Cache with the Duration attribute, in seconds. In the above example,
this page will be rendered on the first request for it, and stored in Cache.
For five seconds, all subsequent requests for this page will be served from the
Cache, which is hugely faster than having to go
through the entire Page lifecycle, possibly combined with database access,
re-render and finally serve the page HTML to the client. After five seconds, the
page will again be rendered (and once again, stored in the Cache).
Do you want to perform a simple test
that will convince you to become a "Cache Convert"? Fire up
Application Center Test or Homer (Web Stress Tool) and throw 100 simultaneous
threads at a sample page that gets a DataSet out of your favorite database and
populates a DataGrid with a DataTable from it. Run this test for one minute,
and note the total number of successful requests for the duration of the test.
Now, modify the page by putting the above OutputCache directive at the top of
the .aspx file. Then run the test again, and compare. It is as objective as
gravity -- caching creates a huge scalability advantage.
The VaryByParam attribute is used to
define parameters that determine which cached copy of a page
should be sent to the browser. If your page doesn't change, you can set this to
"none".
Caching Pages Based on QueryString items
If the contents of one of your pages
can vary based on the value of certain items on the querystring, which is a
common technique in ASP.NET, you can populate the VaryByParam attribute with a
semicolon-delimited list of the QueryString parameter names that control these
changes. For each request, ASP.NET checks the value(s) of these items on the
incoming QueryString, and if the parameter values match those of a previously
cached copy of the result page, it is served from the Cache. If the
parameter(s) don't match, the custom page will be rendered, added to the Cache
with the specified expiration time, and served to the client.
So, for example, if you have userid
and companyid QueryString parameters, your VaryByParam
attribute might look like this:
<%@
OutputCache Duration="5" VaryByParam="userid;companyid"
%>
This technique works automatically with
both QueryString ("GET") parameters as well as Form Field
("POST") data. You can also set the VaryByParam attribute to
"*" to make ASP.NET cache a copy of every possible combination of
querystring parameters. However, this can cause caching of a lot more pages that
you want and can also cause performance problems. You should also be careful
about defining a reasonable expiration time on your cached pages. If you set
the Duration attribute to large values and a large number of unique page
requests come in during this period, you could rack up a lot of server
resources which would actually negatively affect performance or even cause
process recycling based on IIS settings.
Caching pages based on Browser Information
If you need to render a page
differently for different browsers, or you know that ASP.NET will automatically
adjust the rendering of a page automatically based on the browser type and
version of a request, you can use the "VaryByCustom" attribute:
<%@
OutputCache Duration="5" VaryByParam="none" VaryByCustom="browser"
%>
Cache pages Based on Custom Strings
For situations where you want to cache
pages where ASP.NET does not provide built-in support for caching, you can set
the VaryByCustom attribute value to the name of a custom string of your own,
and then override the GetVaryByCustomString method in
global.asax and provide code that creates a unique custom string for the value
that you assigned to the VaryByCustom attribute in your OutputCache directive.
Example code (global.asax):
public override
string GetVaryByCustomString(System.Web.HttpContext context, string custom)
{
string value=null;
if(custom.Equals("urlReferrer")
{
value= context.Request.UrlReferrer; // cache based on where the request came from!
}
}
{
string value=null;
if(custom.Equals("urlReferrer")
{
value= context.Request.UrlReferrer; // cache based on where the request came from!
}
}
Additional Caching Features
Applications that want more control over the HTTP headers
related to caching can use the functionality provided by the
System.Web.HttpCachePolicy class. The following example shows the code
equivalent to the page directives used in the previous samples.
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.SetCacheability(HttpCacheability.Public);
To make this a sliding expiration policy, where the
expiration time out resets each time the page is requested, set the
SlidingExpiration property as shown in the following code. Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetSlidingExpiration(true);
When sliding expiration is enabled
(SetSlidingExpiration(true)), a request made to the origin server always
generates a response. Sliding expiration is useful in scenarios where there are
downstream caches that can satisfy client requests, if the content has not
expired yet, without requesting the content from the origin server. Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetSlidingExpiration(true);
Applications being ported from ASP may already be setting cache policy using the ASP properties; for example:
Response.CacheControl = "Public";
Response.Expires = 60;
For preventing pages from being cached downstream (for example with a logout page) in ASP.NET, you can set the "no-cache" header by using the HttpCachePolicy.SetNoStore method. Put this in your page_load at the latest. You can also set this in your page's HEAD section by adding the following line of code:
<META HTTP-EQUIV="CACHE-CONTROL"
CONTENT="NO-CACHE">
Features such as Output Caching, Fragment Caching, and Cache
Configuration in ASP.NET 2.0 are covered nicely in the QuickStarts,
which you can find online here.
Caching with Database Dependencies
In ASP.NET 1.1, caching based on database
dependencies is not built in. You can still do it, but it requires wiring up
some code. For details on how to do this, here are links to a few articles that
use several different techniques:
- ASP.NET SqlCacheDependency
with SQLte
- ASP.NET SqlCacheDependency
Redux
- ASP.NET Database Cache
Dependency
For SQL Server 2005 and Express, these notifications can be enabled by simply adding the <sqlCacheDependency> element to the web.config, which we'll cover shortly.
For SQL Server 7, 2000 and MSDE, notification events aren't supported, but what we can do is use polling by enabling the database with the command:
aspnet_regsql -S [SERVER] -E -d
[database] -ed
To enable a specific table for notifications, we use:
aspnet_regsql -S [SERVER] -E -d
[database] -et -t [table]
To see all the options for the ASPNET_REGSQL.EXE utility,
use the -? argument.The other way you can enable your database for dependency notifications is to use the methods in the SqlCacheDependencyAdmin class:
SqlCacheDependencyAdmin.EnableNotifications(connectionString);
If you want to see everything that the
above do, simply perform the operation on a new database with one table in it,
and view the results in Enterprise Manager.
The public methods of interest in the
SqlCacheDependencyAdmin class are:
|
DisableNotifications
|
Disables
SqlCacheDependency change notifications for the specified database.
|
|
|
DisableTableForNotifications
|
Overloaded.
Disables SqlCacheDependency change notifications on a SQL Server database
table or an array of database tables.
|
|
|
EnableNotifications
|
Enables
SqlCacheDependency change notifications on the specified database.
|
|
|
EnableTableForNotifications
|
Overloaded.
Connects to a SQL Server database and prepares a database table or tables for
SqlCacheDependency change notifications.
|
|
|
GetTablesEnabledForNotifications
|
Retrieves a
string array containing the name of every table that is enabled for change
notifications in a SQL Server database.
|
|
<caching>
<sqlCacheDependency enabled="true" pollTime="30000">
<databases>
<add name="Pubs" connectionStringName="PubsConn" />
</databases>
</sqlCacheDependency>
</caching>
<sqlCacheDependency enabled="true" pollTime="30000">
<databases>
<add name="Pubs" connectionStringName="PubsConn" />
</databases>
</sqlCacheDependency>
</caching>
The "pollTime" attribute
determines the rate at which the AspNet_SqlCacheTablesForChangeNotification
table is queried to see if any table data has changed. Units are in
milliseconds. The connectionStringName needs to be set to a connectionString in
the <connectionStrings> node of the web.config.
Once the web.config is properly set up,
you can cache pages using the SqlCacheDependency feature by adding this into
the @ outputCache directive in the page:
<%@ outputCache
Duration="86400" VaryByParam="none" SqlDependency =
"databasename:tablename" %>
You can specify multiple databases and tables by providing a
semicolon-delimited list of database:table pairs.
Caching specific Controls in a Page
You can apply the @ outputCache directive to individual user
controls but not to the page itself. At the top of each .ascx user Control
page, place an outputCache directive exactly as you would with a Page. Now if
you have usercontrols whose data does not change frequently or with each page
request, but the data on your page does need to be generated "fresh"
on each request, you can have the best of both worlds. You can even specify the
VaryByControl attribute to name specific controls on your UserControl that
respond to properties such as "headerImage", or
"backgroundColor".
Caching Application Data
Besides the advantages of caching pages, controls and using
SqlCacheDependency, you can also directly interact with the Cache class by
caching frequently used data. A typical pattern that I use looks like this:
public partial class _Default : System.Web.UI.Page
{
protected void
Page_Load(object
sender, EventArgs e)
{
DataTable dt =GetArticlesDt();
this.Repeater1.DataSource = dt;
Repeater1.DataBind();
}
private DataTable GetArticlesDt()
{
DataTable dt = null;
if (Cache["articlesDt"] == null)
{
DataSet
ds = new
DataSet();
string connectionString =
System.Configuration.ConfigurationManager.AppSettings["connectionString"];
SqlCommand cmd = new
SqlCommand("dbo.GetArticles");
cmd.CommandType = CommandType.StoredProcedure;
SqlConnection cn = new
SqlConnection(connectionString);
cmd.Connection = cn;
SqlDataAdapter ad = new
SqlDataAdapter(cmd);
ad.Fill(ds);
dt =
ds.Tables[0];
Cache.Insert("articlesDt", dt, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(30));
}
else
{
dt =
(DataTable)Cache["articlesDt"];
}
return dt;
}
You see I have a method "GetArticlesDt" that
automatically checks the Cache and ensures that we always can get our DataTable
out of Cache in order to avoid repetitive Database calls. Then I just set the
DataSource on the Repeater and DataBind. In addition, you can cache application data based on database dependencies, if you have configured your database as above:
sqlDepcy = new
SqlCacheDependency("ArticlesDB","tblArticles");
Context.Cache.Insert("articlesDt",ds.Tables[0],sqlDepcy);
Context.Cache.Insert("articlesDt",ds.Tables[0],sqlDepcy);
Caching Data Sources
In ASP.NET 2.0, the XmlDataSource,
ObjectDataSource and SqlDataSource controls all support caching "out of
the box":
whateverDataSource.EnableCaching
=true;
whateverDataSource.CacheDuration=5;
I hope this short discussion on Caching has sparked your
interest and provided a convenient way to summarize these concepts for further
study. I use Caching on almost all my work; at Tech-Ed 2004 Rob Howard gave a
presentation on caching that totally blew me away and the lesson was learned
very well. Caching data, pages or controls for as little as one second
can have a dramatic effect on throughput of as much as 500 percent. This may
not seem particularly important if you are just starting out, but I can tell
you from personal experience that when you manage a successful web site with
millions of hits per month, a thorough understanding of the uses and
applications of Caching will serve you very well in your career as a
professional .NET developer. whateverDataSource.CacheDuration=5;
No comments:
Post a Comment