In ASP.NET there are two areas where caching techniques arise:
We'll return to the Cache class later in the article, but let’s focus on Output Caching to start with and Page Output Caching in particular.
You can either declaratively use the Output Caching support available to web forms/pages, page fragments and WebServices as part of their implementation or you can cache programmatically using the HttpCachePolicy class exposed through the HttpResponse.Cache property available within the .NET Framework. I'll not look at WebServices options in any detail here, only mentioning that the WebMethod attribute that is assigned to methods to enable them as Webservices has a CacheDuration attribute which the programmer may specify.
1. Declarative specification via the @OutputCache directive e.g.:
| <%@ OutputCache Duration="120" VaryByParam="none" %> |
2. Programmatic specification via the Cache property of the HttpResponse class, e.g.:
|
Response.Cache.SetExpires(datetime,now,addminutes(2)) Response.Cache.SetCacheability(HttpCacheability.Public) |
In fact @OutputCache is a higher-level wrapper around the HttpCachePolicy class exposed via the HttpResponse class so rather than just being equivalent they ultimately resolve to exactly the same IL code.
Looking at the declarative example and explaining the VaryByParam="none". HTTP supports two methods of maintaining state between pages: POST and GET. Get requests are characterised by the use of the query string to pass parameters, e.g. default.aspx?id=1&name=chris, whereas post indicates that the parameters were passed in the body of the HTTP request. In the example above caching for such examples based on parameters is disabled. To enable, you would set VaryByParam to be ‘name’, for example – or any parameters on which basis you wish to cache. This would cause the creation of different cache entries for different parameter values. For example, the output of default.aspx?id=2&name=maria would also be cached. Note that the VaryByParam attribute is mandatory.
Returning to the programmatic example and considering when you would choose this second method over the first. Firstly, as it’s programmatic, you would use this option when the cache settings needed to be set dynamically. Secondly, you have more flexibility in option setting with HttpCachePolicy as exposed by the HttpResponse.cache property.
You may be wondering exactly what
| Response.Cache.SetCacheability(HttpCacheability.Public) |
We’ll return to Response.Cache after looking at the directive option in more detail.
Note this example requires connectivity to a standard SQLServer installation, in particular the Northwind sample database. You maye need to change the string constant strConn to an appropriate connection string for your system for the sample code presented in this article to work. If you have no easy access to SQLServer, you could load some data in from an XML file or simply pre-populate a datalist (for example) and bind the datagrid to this datastructure.
output_caching_directive_example.aspx
|
<%@ OutputCache Duration="30" VaryByParam="number" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <html> <head></head> <body> <a href="output_caching_directive_example.aspx?number=1">1</a>- <a href="output_caching_directive_example.aspx?number=2">2</a>- <a href="output_caching_directive_example.aspx?number=3">3</a>- <a href="output_caching_directive_example.aspx?number=4">4</a>- <a href="output_caching_directive_example.aspx?number=5">5</a>- <a href="output_caching_directive_example.aspx?number=6">6</a>- <a href="output_caching_directive_example.aspx?number=7">7</a>- <a href="output_caching_directive_example.aspx?number=8">8</a>- <a href="output_caching_directive_example.aspx?number=9">9</a> <p> <asp:Label id="lblTimestamp" runat="server" maintainstate="false" /> <p> <asp:DataGrid id="dgProducts" runat="server" maintainstate="false" /> </body> </html> <script language="vb" runat="server"> const strConn = "server=localhost;uid=sa;pwd=;database=Northwind" Sub Page_Load(sender as Object, e As EventArgs) If Not Request.QueryString("number") = Nothing Then lblTimestamp.Text = DateTime.Now.TimeOfDay.ToString() dim SqlConn as new SqlConnection(strConn) dim SqlCmd as new SqlCommand("SELECT TOP " _ & Request.QueryString("number") & _ " * FROM Products", SqlConn) SqlConn.Open() dgProducts.DataSource = SqlCmd.ExecuteReader(CommandBehavior.CloseConnection) Page.DataBind() End If End Sub </script> |
The full specification of the OutputCache directive is:
|
<%@ OutputCache Duration="#ofseconds" Location="Any | Client | Downstream | Server | None" VaryByControl="controlname" VaryByCustom="browser | customstring" VaryByHeader="headers" VaryByParam="parametername" %> |
Duration
This is the time, in seconds, that the page or user control is cached. Setting this
attribute on a page or user control establishes an expiration policy for HTTP
responses from the object and will automatically cache the page or user control
output. Note that this attribute is required. If you do not include it, a parser error
occurs.
Location
This allows control of from where the client receives the cached document and should
be one of the OutputCacheLocation enumeration values. The default is Any. This
attribute is not supported for @OutputCache directives included in user controls.
The enumeration values are:
Any: the output cache can be located on the browser client (where the request
originated), on a proxy server (or any other server) participating in the request, or
on the server where the request was processed.
Client: the output cache is located on the browser client where the request
originated.
Downstream: the output cache can be stored in any HTTP 1.1 cache-capable devices
other than the origin server. This includes proxy servers and the client that made the
request.
None: the output cache is disabled for the requested page.
Server: the output cache is located on the Web server where the request was
processed.
VaryByControl
A semicolon-separated list of strings used to vary the output cache. These strings
represent fully qualified names of properties on a user control. When this attribute
is used for a user control, the user control output is varied to the cache for each
specified user control property. Note that this attribute is required in a user
control @OutputCache directive unless you have included a VaryByParam attribute. This
attribute is not supported for @OutputCache directives in ASP.NET pages.
VaryByCustom
Any text that represents custom output caching requirements. If this attribute is
given a value of browser, the cache is varied by browser name and major version
information. If a custom string is entered, you must override the
HttpApplication.GetVaryByCustomString method in your application's Global.asax file.
For example, if you wanted to vary caching by platform you would set the custom string
to be ‘Platform’ and override GetVaryByCustomString to return the platform used by the
requester via HttpContext.request.Browser.Platform.
VaryByHeader
A semicolon-separated list of HTTP headers used to vary the output cache. When this
attribute is set to multiple headers, the output cache contains a different version of
the requested document for each specified header. Example headers you might use are:
Accept-Charset, Accept-Language and User-Agent but I suggest you consider the full
list of header options and consider which might be suitable options for your particular
application. Note that setting the VaryByHeader attribute enables caching items in all
HTTP/1.1 caches, not just the ASP.NET cache. This attribute is not supported for
@OutputCache directives in user controls.
VaryByParam
As already introduced this is a semicolon-separated list of strings used to vary the
output cache. By default, these strings correspond to a query string value sent with
GET method attributes, or a parameter sent using the POST method. When this attribute
is set to multiple parameters, the output cache contains a different version of the
requested document for each specified parameter. Possible values include none, *, and
any valid query string or POST parameter name. Note that this attribute is required
when you output cache ASP.NET pages. It is required for user controls as well unless
you have included a VaryByControl attribute in the control's @OutputCache directive.
A parser error occurs if you fail to include it. If you do not want to specify a
parameter to vary cached content, set the value to none. If you want to vary the
output cache by all parameter values, set the attribute to *.
Returning now to the programmatic alternative for Page Output Caching:
output_caching_programmatic_example.aspx
|
<%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <html> <head></head> <body> <a href="output_caching_programmatic_example.aspx?number=1">1</a>- <a href="output_caching_programmatic_example.aspx?number=2">2</a>- <a href="output_caching_programmatic_example.aspx?number=3">3</a>- <a href="output_caching_programmatic_example.aspx?number=4">4</a>- <a href="output_caching_programmatic_example.aspx?number=5">5</a>- <a href="output_caching_programmatic_example.aspx?number=6">6</a>- <a href="output_caching_programmatic_example.aspx?number=7">7</a>- <a href="output_caching_programmatic_example.aspx?number=8">8</a>- <a href="output_caching_programmatic_example.aspx?number=9">9</a> <p> <asp:Label id="lblTimestamp" runat="server" maintainstate="false" /> <p> <asp:DataGrid id="dgProducts" runat="server" maintainstate="true" /> </body> </html> <script language="vb" runat="server"> const strConn = "server=localhost;uid=sa;pwd=;database=Northwind" Sub Page_Load(sender as Object, e As EventArgs) Response.Cache.SetExpires(dateTime.Now.AddSeconds(30)) Response.Cache.SetCacheability(HttpCacheability.Public) Response.Cache.VaryByParams("number")=true If Not Request.QueryString("number") = Nothing Then lblTimestamp.Text = DateTime.Now.TimeOfDay.ToString() dim SqlConn as new SqlConnection(strConn) dim SqlCmd as new SqlCommand("SELECT TOP " _ & Request.QueryString("number") & " * FROM Products", SqlConn) SqlConn.Open() dgProducts.DataSource = SqlCmd.ExecuteReader(CommandBehavior.CloseConnection) Page.DataBind() End If End Sub </script> |
Response.Cache.SetExpires(dateTime.Now.AddSeconds(30))
Response.Cache.SetCacheability(HttpCacheability.Public)
Response.Cache.VaryByParams("number")=true
It is only the third line you’ve not seen before. This is equivalent to VaryByParam="number" in the directive example. Thus you can see that the various options of the OutputCache directive are equivalent to different classes exposed by Response.Cache. Apart from the method of access the pertinent information is, unsurprisingly, very similar to that presented above for the directive version.
Thus, in addition to VaryByParams there is a VaryByHeaders class as well as a SetVaryByCustom method. If you are interested in the extra functionality exposed via these and associated classes I would suggest you peruse the relevant sections of the .NET SDK documentation.
Fragment caching is really a minor variation of page caching and almost all of what we’ve described already is relevant. The ‘fragment’ referred to is actually one or more user controls included on a parent web form. Each user control can have different cache durations. You simply specify the @OutputCache for the user controls and they will be cached as per those specifications. Note that any caching in the parent web form overrides any specified in the included user controls. So, for example, if the page is set to 30 secs and the user control to 10 the user control cache will not be refreshed for 30 secs.
It should be noted that of the standard options only the VaryByParam attribute is valid for controlling caching of controls. An additional attribute is available within user controls: VaryByControl, as introduced above, allowing multiple representations of a user control dependent on one or more of its exposed properties. So, extending our example above, if we implemented a control that exposed the SQL query used to generate the datareader which is bound to the datagrid we could cache on the basis of the property which is the SQL string. Thus we can create powerful controls with effective caching of the data presented.
The public properties and methods of the cache class are:
Public Properties
Count: gets the number of items stored in the cache.
Item: gets or sets the cache item at the specified key.
Public Methods
Add: adds the specified item to the Cache object with dependencies, expiration and priority policies, and a delegate you can use to notify your application when the inserted item is removed from the Cache.
Equals: determines whether two object instances are equal.
Get: retrieves the specified item from the Cache object.
GetEnumerator: retrieves a dictionary enumerator used to iterate through the key settings and their values contained in the cache.
GetHashCode: serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures like a hash table.
GetType: gets the type of the current instance.
Insert: inserts an item into the Cache object. Use one of the versions of this method to overwrite an existing Cache item with the same key parameter.
Remove: removes the specified item from the application's Cache object.
ToString: returns a String that represents the current Object.
We'll now examine some of the above to varying levels of detail, starting with the most complex, the insert method:
Overloads Public Sub Insert(String, Object)
Inserts an item into the Cache object with a cache key to reference its location and using default values provided by the CacheItemPriority enumeration.
Overloads Public Sub Insert(String, Object, CacheDependency)
Inserts an object into the Cache that has file or key dependencies.
Overloads Public Sub Insert(String, Object, CacheDependency, DateTime, TimeSpan)
Inserts an object into the Cache with dependencies and expiration policies.
Overloads Public Sub Insert(String, Object, CacheDependency, DateTime, TimeSpan, CacheItemPriority, CacheItemRemovedCallback)
Inserts an object into the Cache object with dependencies, expiration and priority policies, and a delegate you can use to notify your application when the inserted item is removed from the Cache.
Summary of parameters:
| String | the name reference to the object to be cached |
| Object | the object to be cached |
| CacheDependency | file or cache key dependencies for the new item |
| Datetime | indicates absolute expiration |
| Timespan | sliding expiration – object removed if greater than timespan after last access |
| CacheItemPriorities | an enumeration that will decide order of item removal under heavy load |
| CacheItemPriorityDecay | an enumeration; items with a fast decay value are removed if not used frequently |
| CacheItemRemovedCallback | a delegate that is called when an item is removed from the cache |
Picking out one of these options for further mention: CacheDependency. This attribute allows the validity of the cache to be dependent on a file or another cache item. If the target of such a dependency changes, this can be detected. Consider the following scenario: an application reads data from an XML file that is periodically updated. The application processes the data in the file and represents this via an aspx page. Further, the application caches that data and inserts a dependency on the file from which the data was read. The key aspect is that when the file is updated .NET recognizes the fact as it is monitoring this file. The programmer can interrogate the CacheDependency object to check for any updates and handle the situation accordingly in code.
| Cache.Remove(“MyCacheItem”) |
|
Cache.Insert(“MyCacheItem”, Object) Dim obj as object obj = Cache.get(“MyCacheItem”) obj = Cache.Item("MyCacheItem") obj = Cache(“MyCacheItem”) |
|
dim myEnumerator as IDictionaryEnumerator myEnumerator=Cache.GetEnumerator() While (myEnumerator.MoveNext) Response.Write(myEnumerator.Key.ToString() & “<br>”) 'do other manipulation here if so desired End While |
cache_class_example.aspx
|
<%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <html> <head></head> <body> <asp:datagrid id="dgProducts" runat="server" maintainstate="false" /> </body> </html> <script language="vb" runat="server"> public sub Page_Load(sender as Object, e as EventArgs) const strConn = "server=localhost;uid=sa;pwd=;database=Northwind" dim dsProductsCached as object = Cache.Get("dsProductsCached") if dsProductsCached is nothing then Response.Write("Retrieved from database:") dim dsProducts as new DataSet() dim SqlConn as new SqlConnection(strConn) dim sdaProducts as new SqlDataAdapter("select Top 10 * from products", SqlConn) sdaProducts.Fill(dsProducts, "Products") dgProducts.DataSource = dsProducts.Tables("Products").DefaultView Cache.Insert("dsProductsCached", dsProducts, nothing, _ DateTime.Now.AddMinutes(1), TimeSpan.Zero) else Response.Write("Cached:") dgProducts.DataSource = dsProductsCached end if DataBind() end sub </script> |
Professional ASP.NET
Sussman et al.
Wrox
.NET SDK documentation
Various online articles
You may run
output_caching_directive_example.aspx here.
You may run
output_caching_programmatic_example.aspx here.
You may run
cache_class_example.aspx here.
You may download the code here.