April 26, 2001
In this month's column we're going to look at another one of the new features in ASP.NET—caching. In Beta 1, there are two distinct caching features—Page output caching and the new Cache object. Beta 2 of ASP.NET will introduce several more caching features that we'll discuss in this column after Beta 2 is released.
Caching is the process of storing frequently used data, usually data that is costly to generate, for reuse. Typically this data is stored in memory since retrieving data from memory is much more efficient than retrieving the data from other locations, such as a database.
ASP.NET has several facilities for supporting caching: a Cache API for storing arbitrary data and an Output Cache used to store frequently requested Pages.
Let's discuss a quick example.
A catalog used on an e-commerce site might only change once a week. An ASP.NET Web application could be built that provides a front-end interface for that catalog, allowing customers to purchase products. When a customer is simply browsing the catalog, the system is making (in most cases) network calls to a back-end database server. The database server is also doing calculations on the data, such as a join query, and returning results.
This type of configuration is quite common, be it a catalog or some other type of commonly requested data from a database. However, the design in the above example can be improved upon. We know that the data in the database only changes once a week, and we know that there are several performance costs associated with retrieving the data:
- Executing of the ASP.NET code to make the database request.
- Use of the network for the Web server to communicate with the database server.
- Work done on the database server to compile and execute the query (or simply execute stored procedure).
Caching allows us to eliminate much of the above work and improve the performance and scalability of our application. We can improve performance by caching the results and serving them statically (versus dynamically on each request) and our scalability increases since we're using fewer resources to service each request.
Beta 1 of ASP.NET introduced the Cache API as well as Page output caching, which uses the Cache API. Let's discuss these two features in more detail.
Storing frequently requested data in memory is nothing new for ASP developers. We've had two types of objects that solve this problem:
- Session objects
- Application objects
Session 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 to remains identical in function (key/value pairs). For example, we 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. We simply name a key, in this case we used
SomeInterestingData, and assign it a value, in this case the string
ASP.NET introduces another key/value pair object—Cache. In addition to simply storing key/value pairs, Cache adds 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 will automatically expire if they are underused.
- Support for Callback—Code paths can be added that will be called when an item is removed from the Cache, giving us an opportunity to update the Cache or not remove the item.
When we code our applications to use the Cache we do have to make one consideration:
Always check if the item exists in the Cache before attempting to use it.
Because the Cache will expire, items based on dependencies or under use, we must always write our code to create or retrieve the item we need if it doesn't exist within the Cache.
For example, if we wrote a function that returned 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=<user id>;pwd=<password>;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
We could easily write code that took advantage of the Cache. Executing LoadDataSet() only when the resulting DataSet is not already in the Cache:
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. We essentially could accomplish exactly this same thing. Where this gets interesting is when we set up dependencies.
Dependencies allow us 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. Let's look at each of these dependencies.
File-based dependency invalidates a particular Cache item when file(s) on disk change. For example, if instead of loading our ProductData from a database, we loaded it from an XML file:
Dim dom As XmlDocument() dom.Load(Server.MapPath("product.xml") Cache("ProductData") = dom
We obviously would also want to invalidate the data within the Cache if
product.xml changes. Assuming
product.xml is in the same directory as the requesting application:
Dim dependency as new CacheDependency(Server.MapPath("product.xml")) Cache.Insert("ProductData", dom, dependency)
In the above code sample, we're creating an instance of a CacheDependency class, dependency, and passing in the path to the product.xml file. We then use the Insert() method of the Cache to create our ProductData key that is dependent upon the file it retrieves its data from.
Key-based dependency invalidates a particular Cache item when another Cache item changes. For example, if our application added multiple DataSets to the Cache, such as
MarketingData rely upon
ProductData being valid. We could use a key-based dependency to invalidate
ProductData changes. We set up this dependency when we create the Cache entries for
Dim dependency (1) As String dependencyKey(0) = "ProductData" Dim productDataDependency As new CacheDependency(nothing, dependencyKey) Cache.Insert("SalesData", LoadDataSet("Sales"), productDataDependency)
In the above example code, we use the Insert() method to create a new Cache entry named
SalesData passing in an instance of a CacheDependency class named
productDataDependency which names the Cache key
ProductData. Now, whenever
SalesData will be removed from the Cache.
Time-based dependency simply expires the item at a defined point in time. Again, we would use the Insert() method of the Cache to create this dependency. We have two options for time-based dependency:
- Absolute—Sets an absolute time; for example, current time + 10 minutes for the Cache entry to expire.
- Sliding—Resets the time for the item in the Cache to expire on each request.
We can use the Sliding expiration option to Cache our
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, hopefully you've seen just how easy it is to use. Underneath the covers, ASP.NET makes use of the Cache API and it's time based expiration policy to support the concept of page output caching. In a future column will dig into more detail on the Cache API.
Page Output Caching
Page output caching is a feature of ASP.NET that allows for the entire contents of a given page to be stored in the Cache. We've been using the Cache API in the above example to store DataSets in the Cache. What if, rather than simply storing the DataSet, we determine that the entire page (that displays the results of the DataSet) can be cached? The benefit here is that rather than dynamically executing the ASP.NET page on each request, we serve it statically from memory. This can be a huge performance enhancement!
There is both a low-level and high-level API for working with Page Output Caching. We're only going to discuss the high-level API in the remainder of this article, but we'll discuss the low-level API in a future column (post Beta 2).
The high-level API consists of a Page directive that instructs ASP.NET to Cache the results from the page for a period of time:
<%@ OutputCache Duration="10" %>
This directive, added to the top of an ASP.NET page, simply instructs ASP.NET to cache the results of the page for 10 seconds. After 10 seconds the page will re-execute. Here's an example that uses this directive:
<%@ OutputCache Duration="10" %> <Script runat="server"> Public Sub Page_Load() span1.InnerHtml = DateTime.Now.ToString("r") End Sub </Script> <font size=6>The time is: <font color=red><span id="span1" runat="server"/></font></font>
This page simply executes on the first request, displays the time at which it was requested, and then is served from the Cache for a period of 10 seconds. For example, if we request the page at 10:30:12, we would see that value for the output for 10 seconds, after which the page is re-executed.
The Cache is a new feature for us ASP developers. However, it is both easy to use and powerful. The API is similar to Application and Session, a simple key/value pair dictionary, but unlike Application or Session, items within the Cache can expire. Unlike Application, the Cache supports file, key, and time based dependencies, as well as callbacks, which we'll save for our post Beta 2 discussion. The Cache is also used by ASP.NET for ASP.NET Page Output Caching. In a future column we'll discuss some of the new Cache features that will be appearing in Beta 2, such as Partial Page Caching and Web Service Caching, and we'll also look at some of the more advanced features like callbacks.
Rob Howard is a program manager for ASP.NET on the .NET Framework team. He spends whatever spare time he has either with his family or fly fishing in Eastern Washington.