Monday, March 19, 2012

Caching

What is caching?
High-performance Web applications should be designed with caching in mind. Caching is the technique of storing frequently used items in memory so that they can be accessed more quickly. Caching is important to Web applications because each time a Web form is requested, the host server must process the Web form’s HTML and run Web form code to create a response. By caching the response, all that work is bypassed. Instead, the request is served from the reponse already stored in memory.
Caching an item incurs considerable overhead, so it’s important to choose the items to cache wisely. A Web form is a good candidate for caching if it is frequently used and does not contain data that frequently changes. By storing a Web form in memory, you are effectively freezing that form’s server-side content so that changes to that content do not appear until the cache is refreshed

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!
}
}

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.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.

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

In ASP.NET 2.0 we have the ability to cache pages with data dependencies based on SQL Server 7.0, 2000, 2005, MSDE, and SQL Express editions. The capabilites vary by application and version. In all cases, whether with ASP.NET 1.1 or ASP.NET 2.0, the key to database cache dependency is being able to know when data in the database changes.

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.

After your database is configured, you need to add the <sqlCacheDependency> element:

<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);

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.

No comments:

Post a Comment