Caching

   

One of the biggest performance wins you can get for your application results from the judicious use of caching. If, for example, you have a page that is dynamically created but is the same for the entire day, you will see dramatic performance increases if you cache this page. If it is cached, it has to be generated only once, and after that simply served up without the computation it takes to dynamically generate it. You can have the cache expire when the date changes and when the content needs to be regenerated.

The following sections discuss three approaches to caching: page caching, the cache API, and fragment caching. You'll then see a number of caching examples.

Page Caching

Page output caching is a feature of ASP.NET that enables the entire contents of a given page to be stored in the cache. The preceding example used the cache API to store DataSet s in the cache. What if, rather than simply storing the DataSet , you could cache the entire page that displays the results of the DataSet ? The benefit here is that rather than dynamically executing the ASP.NET page on each request, it can be served statically from memory. This can be a huge performance enhancement.

ASP.NET enables you to declaratively control the output caching policies of an ASP.NET page or a user control contained in a page. Page caching can be controlled with a page directive, as the following syntax illustrates (the attributes are all shown and described in Table 21.2):

 <%@ OutputCache Duration="#ofseconds"     Location="Any  Client  Downstream  Server  None"    VaryByControl="controlname" VaryByCustom="browser  customstring"    VaryByHeader="headers"    VaryByParam="parametername" %> 
Table 21.2. The Page Caching Attributes

Attribute

Description

Duration

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 automatically caches the page or user control output. This attribute is required. If you do not include it, a parser error occurs.

Location

One of the OutputCacheLocation enumeration values. The default is Any . This attribute is required when you output cache ASP.NET pages and user controls. A parser error occurs if you fail to include it.

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.

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

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. This attribute is required when you output cache ASP.NET pages and user controls. 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 * .

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. This attribute is not supported for @ OutputCache directives in ASP.NET pages.

Setting values for the page output cache is the same as manipulating the HttpCachePolicy.SetExpires and HttpCachePolicy.SetCacheability methods through the HttpResponse.Cache property. Setting the VaryByParam attribute when creating a user control implements partial-page caching for that control.

If a Web Forms page requires authorization if it is to be viewed by a user, the output cache sets the Cache-Control header to private .

The following example demonstrates how you can set the duration that a page or user control is output-cached:

 <%@ OutputCache Duration="10" VaryByParam="none" %> 

The next example demonstrates how you can instruct the output cache to cache a page or user control by the location and count form parameters from a form's POST or from a query string. Each HTTP request that arrives with a different location or count parameter (or both) is cached for 10 seconds. Any subsequent requests with the same parameter values are satisfied from the cache until the entry expires .

 <%@ OutputCache Duration="10" VaryByParam="location;count" %> 

Cache API

Storing frequently requested data in memory is nothing new for ASP developers, who have had two types of objects that solve this problem: Session objects and Application objects.

The Session object is used to store per-user data across multiple requests. There have been some changes for Session in ASP.NET, but most of these changes are application-level changes and don't affect the way Session is used; that is, it's still a simple key/value pair.

The Application object from ASP is also carried forward to ASP.NET, and it too remains identical in function (key/value pairs). For example, you could write the following in either ASP or ASP.NET:

 Application("SomeInterestingData") = "Example data"  Response.Write(Application("SomeInterestingData") 

The same semantics are used for Session . Simply name a key (in this case SomeInterestingData ) and assign it a value (in this case the string " Example data" ).

ASP.NET introduces another key/value pair: the object Cache . In addition to simply storing key/value pairs, Cache adds several types of additional functionality specifically designed to store transient data:

  • Dependencies ” A key added to the cache can set up dependency relationships that can force that key to be removed from the cache. The supported dependencies include key, file, and time.

  • Automatic Expiration ” Underused items added to the cache that have no dependencies automatically expire if they are underused.

  • Support for Callback ” Code paths can be added that are called when an item is removed from the cache, which gives you an opportunity to update the cache or not remove the item.

Note

When you code applications to use the cache you do have to make one consideration: Always check whether the item exists in the cache before attempting to use it.


Because the cache will expire, code for items that either use or depend on cached objects must always include code to create or retrieve the item needed if it doesn't exist within the cache.

For example, you might use the following code to write a function that returns a populated DataSet :

 Private Function LoadDataSet() As DataSet    Dim sqlConnection As SQLConnection    Dim sqlAdapater As SQLDataSetCommand    Dim datasetProducts As New DataSet()    Dim sqlDSN As String    Dim sqlSelect As String    ' Connection String and Select statement    sqlDSN = "server=localhost;uid=sa;pwd=;database=grocertogo"    sqlSelect = "Select * From Products"    ' Connect    sqlConnection = new SQLConnection(sqlDSN)    sqlAdapater = new SQLDataSetCommand(sqlSelect, sqlConnection)    ' Fill dataset create product table    sqlAdapter1.FillDataSet(datasetProducts, "products")    Return products  End Function 

It's easy to write code that takes advantage of the cache. To execute LoadDataSet() only when the resulting DataSet is not already in the cache, use the following:

 Public Function GetProductData() As DataSet    If (IsNothing(Cache("ProductData")) Then      Cache("ProductData") = LoadDataSet()    Return Cache("ProductData")  End Function 

However, this isn't really all that different from using Application variables . We essentially could accomplish exactly this same thing with Application variables as we have with Cache objects. Things get interesting, though, when you need to set up dependencies.

Cache Dependencies

Dependencies make it possible to invalidate a particular item within the cache based on changes to files, changes to other cache keys, or at a fixed point in time. Dependencies can be file-based , key-based, or time-based.

File-Based Dependency

File-based dependency invalidates a particular cache item when files on a disk change. For example, suppose that ProductData were to be loaded from a database rather than from an XML file:

 Dim dom As XmlDocument()  dom.Load(Server.MapPath("product.xml")  Cache("ProductData") = dom 

Obviously, the data within the cache should be invalidated if product.xml changes. Assuming product.xml is in the same directory as the requesting application, the code would be as follows :

 Dim dependency as new CacheDependency(Server.MapPath("product.xml"))  Cache.Insert("ProductData", dom, dependency) 

The preceding code sample creates an instance of a CacheDependency class, dependency , and passes in the path to the product.xml file. It then uses the Insert() method of the cache to create the ProductData key that is dependent upon the file from which it retrieves its data.

Key-Based Dependency

Key-based dependency invalidates a particular cache item when another cache item changes. For example, this would occur if the sample application added multiple DataSets to the cache ”such as ProductData , SalesData , and MarketingData ”and SalesData and MarketingData relied upon ProductData being valid. A key-based dependency could be used to invalidate SalesData and MarketingData if ProductData changes. You set up this dependency when you create the cache entries for SalesData and MarketingData :

 Dim dependency (1) As String  dependencyKey(0) = "ProductData"  Dim productDataDependency As new CacheDependency(nothing, dependencyKey)  Cache.Insert("SalesData", LoadDataSet("Sales"), productDataDependency) 

In the preceding sample code, the Insert() method is used to create a new cache entry named SalesData , which passes in an instance of a CacheDependency class named productDataDependency , which names the cache key ProductData . Now, whenever ProductData changes, SalesData is removed from the cache.

Time-Based Dependency

Time-based dependency simply expires the item at a defined point in time. Again, the Insert() method of the cache is used to create this dependency. There are two options for time-based dependency:

  • Absolute ” Sets an absolute time; for example, the cache entry is to expire at current time + 10 minutes.

  • Sliding ” Resets the time for the item in the cache to expire on each request.

You can use the sliding expiration option to cache the ProductData DataSet for a maximum of 10 minutes. As long as requests for ProductData are made within a 10-minute window, the data is made valid for another 10 minutes:

 ' 10 minute time span  Dim span As New TimeSpan(0,10,0)  Cache.Insert("ProductData", LoadDataSet(), nothing, nothing, span) 

Although this has been a relatively lightweight discussion of the cache API, you should have seen just how easy it is to use. Underneath the covers, ASP.NET uses the cache API and its time-based expiration policy to support the concept of page output caching.

Fragment Caching

Sometimes, it is not practical to cache an entire page. Many times, portions of the page must be created or customized for each request. In this case, it can be worthwhile for you to identify which objects or data associated with the page request will require significant server resources to construct. After you identify these items, you can isolate them from the rest of the page and cache them for a period of time that you specify to conserve server resources. This is known as fragment caching .

This technique enables you to separate portions of a page that take valuable processor time to create ”such as database queries ”from other parts of the page. You can choose to allow parts of the page that require fewer server resources to be generated dynamically for each request.

To use fragment caching, you must develop a Web Forms user control for the portion of the page that you want to cache and include an @ OutputCache directive in the user control (.ascx file) with the duration and any of the optional attributes that alter the user control output. For example, if you include the following directive at the top of a user control file, a version of the control is stored in the output cache for 120 seconds:

 <%@ OutputCache Duration="120" %> 

Only the user control is cached when you do this. When a user control with an @ OutputCache directive is declared and used within a page, the ASP.NET page parser instantiates and adds an OutputCacheControl into the logical position a user control. And this user control would otherwise occupy in the tree if it were not cached.

Because you can nest user controls in other server controls on a page, you also can nest user controls that have been placed in the output cache. This means that you can include an output cache directive in a user control that is inside an output-cached page, or in a user control that is part of another output-cached user control.

You can declare an ID attribute in a user control tag that you have specified for output caching to program against that instance of the user control. However, you must explicitly verify the existence of the user control in the output cache so that the code will work properly.

For example, if you declaratively assign a user control the ID MyUserControl , you can check for its existence with the following code in the containing .aspx file's code declaration block:

C# Example

 void Page_Load()  {      if( MyUserControl != null )      {          MyUserControl Do Something;      }  } 

VB Example

 Sub Page_Load()      If MyUserControl <> Null Then         MyUserControl Do Something      End If  End Sub 

If you write code to manipulate a user control that contains an @ OutputCache directive, an error occurs. A user control that is output cached is dynamically generated only for the first request; any subsequent requests are satisfied from the output cache until the control expires. Any programmatic manipulation that must occur to create the content of the user control must be included in the control. You can include this logic in one of the page lifecycle events associated with the user control, such as in the Page_Load event or Page_PreRender event.

Caching Examples

The following examples came out of a summit that I attended. We worked through them as a group , and I've based these examples on the ones that were created in those presentations. Thanks to Rob Howard for his permission to use the examples.

The first two examples shown in Listings 21.1 and 21.2 display the time. Even when the page is refreshed, it continues to display the same time. It expires, though, after 10 seconds. After this happens, the time that's displayed changes because the DateTime.Now property is requeried.

Listing 21.1 C# Example ”Displaying for 10 Seconds
 <%@ Page Language="C#" %>  <%@ OutputCache Duration="10" VaryByParam="none"%>  <Script runat="server">    public void Page_Load()    {      // Get the Date and Time, once again this      // should not change after the first run      DateTime NowTime = DateTime.Now;      DateTime Expires = NowTime.AddSeconds( 10 );      CreatedStamp.InnerHtml = NowTime.ToString( "r" );      ExpiresStamp.InnerHtml = Expires.ToString( "r" );    }  </Script>  <Font size="6">Output caching for 10 seconds...      <HR size="1">      Output Cache created: <Font color="red">             <B id="CreatedStamp" runat="server"></B></Font>      <BR>      Output Cache expires: <Font color="red">             <B id="ExpiresStamp" runat="server"></B></Font>  </Font> 
Listing 21.2 VB Example ”Displaying for 10 Seconds
 <%@ OutputCache Duration="10" VaryByParam="none"%>  <Script runat="server">    Public Sub Page_Load()      CreatedStamp.InnerHtml = Now()      ExpiresStamp.InnerHtml = Now().AddSeconds( 10 )    End Sub  </Script>  <Font size="6">Output caching for 10 seconds...      <HR size="1">      Output Cache created: <Font color="red">             <B id="CreatedStamp" runat="server"></B></Font>      <BR>      Output Cache expires: <Font color="red">             <B id="ExpiresStamp" runat="server"></B></Font>  </Font> 

Listings 21.3 and 21.4 show how a page can be cached and altered only by the browser.

Listing 21.3 C# Example ”This Page Is Cached Until Requested by Another Browser
 <%@ OutputCache Duration="30" VaryByParam="none" VaryByCustom="browser" %>  <Script language="C#" runat="server">      public void Page_Load()      {          browser.InnerHtml = Request.Browser.Browser;          timestamp.InnerHtml = DateTime.Now.ToString( "r" );      }  </Script>  <Font size=6>  Varying output by browser: <B><Font color="red"     id="browser" runat="server"/></B>  <BR>  Added to the cache: <B><Font color="red" id="timestamp" runat="server" /></B>  </Font> 
Listing 21.4 VB Example ”This Page Is Cached Until Requested by Another Browser
 <%@ OutputCache Duration="60" VaryByParam="none" VaryByCustom="browser" %>  <Script runat="server">      Public Sub Page_Load          browser.InnerHtml = Request.Browser.Browser          timestamp.InnerHtml = Now()      End Sub  </Script>  <Font size=6>  Varying output by browser: <B><Font color="red"     id="browser" runat="server"/></B>  <BR>  Added to the cache: <B><Font color="red" id="timestamp" runat="server" /></B>  </Font> 
   


Special Edition Using ASP. NET
Special Edition Using ASP.Net
ISBN: 0789725606
EAN: 2147483647
Year: 2002
Pages: 233

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net