Output Caching with Dependency in ASP.NET
Richard Harris
DevOps, Online & Mobile at TD Bank Group #devops #agile #cloud #java #js #csharp
Introduction
The most performance-critical area in most web applications is the data layer. But many ASP.NET developers don’t realize that you can dramatically reduce the burden on your database and increase the scalability of all your web applications with just a little caching code.
ASP.NET has two types of caching. Your applications can and should use both types, because they complement each other:
- Output caching: This is the simplest type of caching. It stores a copy of the final rendered HTML page that is sent to the client.
- Data caching: This is carried out manually in your code. To use data caching, you store important pieces of information that are time-consuming to reconstruct (such as a DataSet retrieved from a database) in the cache.
Here are two caching guidelines to keep you on the right track:
- Cache data (or web pages) that are expensive: In other words, cache information that’s time-consuming to create. The results of a database query or contents of a file are good examples. Not only does it take time to open a database connection or a file, but it can also delay or lock out other users who are trying to do the same thing at the same time.
- Cache data (or web pages) that are used frequently: There’s no point setting aside memory for information that’s never going to be needed again. For example, you might choose not to cache product detail pages, because there are hundreds of different products, each with its own page. But it makes more sense to cache the list of product categories, because that information will be reused to serve many different requests.
Output Caching
Output Caching allows you to store the output of an entire Page or UserControl via the OutputCache directive. This article covers Programmatic Output Caching with a File Dependency.
Typical implementations include:
- Basic Output Caching
- Caching on the Client Side (i.e. personalized data)
- Specific Query String Parameter
- Caching with Several Query String Parameters
- Fragment Caching (usage of OutputCache directive in a user control)
- Cache Profiles (web.config settings for group of pages)
<%@ OutputCache Duration="30"
VaryByParam="None | ParamName | Param1;Param2 | *" %>
Here is a list of various parameters:
<%@ OutputCache Duration="#ofseconds"
Location="Any | Client | Downstream | Server | None | ServerAndClient "
Shared="True | False"
VaryByControl="controlname"
VaryByCustom="browser | customstring"
VaryByHeader="headers"
VaryByParam="parametername"
VaryByContentEncoding="encodings"
CacheProfile="cache profile name | ''"
NoStore="true | false"
SqlDependency="database/table name pair | CommandNotification"
%>
Data caching allows for caching with dependencies. There are 3 types of dependencies:
- files/folder
- other cached items
- database query via Service Broker notifications
However, as you can see from the Parameters above, there is no option for File Dependency. Plus, your MS SQL Server instance may not always have the Service Broker enabled. This is the scenario being addressed in this article. In some cases you may be able to create a solution using VaryByCustom. However, I found that by adding OutputCaching via code rather than Page directive, I could implement it with a File Dependency. Take a look:
// Programmatic Output Cache
Response.Cache.SetExpires(DateTime.Now.AddMinutes(30));
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetValidUntilExpires(true);
Response.Cache.VaryByParams["PageID"] = true;
string fileDependencyPath = Server.MapPath("~/cmsAssets/DebugPublished.txt");
Response.AddFileDependency(fileDependencyPath);
Example
Recently I worked on a project where I added Fragment Caching to a UserControl that had a CacheDependency applied. This example shows how to:
- Remove Output Cache on a particular page
- Use CachePolicy to add a CacheDependency to my OutputCache (closest thing within the OutputCache tag properties would be VaryByCustom="customstring")
- Dynamically inject a UserControl into a MasterPage (i.e. allows you to conditionally add the control)
- Use Global.asax Session_Start as a suitable place to expire the cache object that is the dependency object for our OutputCache. This was a suitable place to trigger checking some conditions and Inserting a new cache object (expiring the previous dependency in the process) if suitable. I could have done this somewhere else, such as a explicit website visitor action, or approval of a page in the Content Management System being used.
ControlFile.ascx:
<%@ OutputCache Duration="3600" VaryByParam="PageID" %>
ControlFile.ascx.cs:
protected void Page_Load(object sender, EventArgs e)
{
if (Request.RawUrl.ToLower().Contains("custom.aspx"))
{
// Programmatic Output Cache - Force Expire
// Page.Response.Cache.SetCacheability(HttpCacheability.NoCache);
HttpResponse.RemoveOutputCacheItem("/custom.aspx");
pages.DataSource = ReportTools.Utils.GetCustomReport();
}
else
{
// Caching - Expire OutputCache
this.CachePolicy.Dependency = new System.Web.Caching.CacheDependency(null,new string[] { "PublishHistoryCount" });
pages.DataSource = ReportTools.Utils.GetSlides(ReportTools.Utils.CategoryID);
}
pages.DataBind();
}
InnerContent.master.cs:
protected override void OnInit(EventArgs e)
{
PartialCachingControl tc = (PartialCachingControl)LoadControl("../../cmsAssets/CustomUserControls/ControlFile.ascx");
PlaceHolder1_1.Controls.Add(tc);
base.OnInit(e);
this.MyPlaceHolders.Add(PlaceHolder1);
}
Global.asax:
void Session_Start(object sender, EventArgs e)
{
if(HttpContext.Current.Cache["PublishHistoryCount"] == null)
{
HttpContext.Current.Cache.Insert("PublishHistoryCount", ReportTools.Utils.PublishHistoryCount, null,
System.DateTime.MaxValue, System.TimeSpan.Zero,
System.Web.Caching.CacheItemPriority.NotRemovable,
null);
}
else
{
int phc = (int)HttpContext.Current.Cache["PublishHistoryCount"];
if(phc != ReportTools.Utils.PublishHistoryCount)
{
HttpContext.Current.Cache.Insert("PublishHistoryCount", ReportTools.Utils.PublishHistoryCount, null,
System.DateTime.MaxValue, System.TimeSpan.Zero,
System.Web.Caching.CacheItemPriority.NotRemovable,
null);
}
}
}
Key References
- ASP.NET Caching - CodeProject
- ASP.NET Page Output Cache - CodeProject
- Ten Caching Mistakes that Break your App - CodeProject
- OutputCache - The complete ASP.NET WebForms tutorial
- OutputCache - more examples - The complete ASP.NET WebForms tutorial
- High-Performance ASP.NET Caching -- Visual Studio Magazine
- ASP.Net MVC Framework Server-Side HTML Caching Techniques
- Clear user control output cache from another page | The ASP.NET Forums
- Remove ASP.NET Page Output Cache Entries: ASP Alliance
- Invalidating OutputCache programmatically on UserControl - Stack Overflow
MSDN: