When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, June 9, 2004

HttpContext.Items - a Per-Request Cache Store

By Scott Mitchell


Introduction


Each time a Web server receives a request for an ASP.NET resource - be it an ASP.NET Web page, a Web service, or any other request that is mapped in IIS to the ASP.NET engine - an instance of the System.Web.HttpContext object is created. This class contains information about the request. For example, the HttpContext class has the "intrinsic" objects you're familiar with: Request, Response, Session, Application, Server, and Cache. HttpContext contains information about the current user making the request in its User property, it stores information about any errors that have occurred in its Errors property.

- continued -

One property of the HttpContext object that not many ASP.NET developers know about is the Items property, which is a Hashtable for storing and retrieving information on a per-request basis. That is, the various objects that participate in an ASP.NET request - the ASP.NET Web page, User Controls, custom compiled controls, HTTP modules, and class libraries used during the page request - all have access to the HttpContext object and therefore can all read from and write to the Items Hashtable. This Hashtable, then, proves useful as a centralized data store that lasts only the length of the request that all participants involved in the request can access.

The Items Hashtable can be employed as a per-request cache store, acting as a repository for data that is shared among disparate actors in the ASP.NET request life cycle. This article was inspired by Rob Howard's TechEd 2004 presentation BlackBelt ASP.NET, whose slides and code can be downloaded from this blog entry; during Rob's talk I kept an outline of the presentation, which is available on my blog.

Scenarios Where HttpContext.Items Caching Makes Sense


While all of the logic and user interface elements for an ASP.NET Web page can be placed entirely within the ASP.NET Web page and code-behind class itself, many developers wisely use User Controls, custom compiled controls, and custom class libraries to create a solid application architecture that's easy to understand, maintain, and update. While this approach is ideal, it can be a bit frustrating if, after componentizing your pages, you find that each individual unit needs to work with similar data.

For example, imagine you were building an eCommerce Web site. On each page there may be various User Controls or compiled custom controls that displayed information about the current product, reviews of the product, the logged-in user's information, and so forth. A myriad of these controls might need to access similar bits of information from the database. The page might have one User Control that displays information about the current product, with another User Control displaying information about the product's manufacturer. Since this information is abstracted into two separate User Controls, each User Control must hit the database to retrieve the pertinent information. However, since the product and its manufacturer are related, this information could be easily brought back with one query using a JOIN statement.

There are even cases where database access is quite needlessly replicated. Perhaps at the top of the page there's a User Control that displays a brief summary of the user's shopping cart. This control might be repeated at the bottom. Both instances of the controls would need to access the database, thereby taking two queries instead of one.

In all of these examples, there's a common thread - by dividing the page into pieces the separate pieces are unable to easily share their repeated or similar bits of information, information that may require an expensive database access. Ideally, these separate page building blocks would be capable of sharing this information. Well, they can, by using the HttpContext class's Items Hashtable.

Examining the HttpContext class's Items Hashtable


In order to improve performance, it makes that we'd want to cache the shared bits of information among the various pieces of an ASP.NET Web page. But where should this information be cached? One option would be to use ASP.NET's data cache - after all, we have access to this Cache via the HttpContext object. This data cache is a good choice if you are working with data that is fairly static and is common across all users and all pages. But the data you may be working with might be unique to each user, or might be changing often.

For More on the ASP.NET Data Cache...
For more information on ASP.NET's data cache, be sure to check out Scott McFarland's article Caching with ASP.NET, which examines all of ASP.NET's caching techniques - output caching, fragment caching, and data caching.

For such situations, we can cache the data on a per-request basis. That is, if we have two User Controls on the page that need the same database data, rather than having them both hit the database, whoever hits the database first can cache the data for that request. The second User Control, then, can simply reference this cached data. The HttpContext class's Items collection provides a repository for stashing objects on a per-request basis.

To accomplish this, the User Controls and custom compiled controls need to access their data by first checking in the HttpContext.Items Hashtable. If the data is found there, then great, time saved calling the database. If not, the User Control or compiled control needs to get the data from the database and then add it to the Hashtable. Imagine that you had a set of custom business objects, one class being a UserInfo class, which contains properties pertinent to a user for your site. Furthermore, assume that there's a data access layer class, DAL, with a static method called GetUserByUserId(int), which takes in an integer user ID and returns a UserInfo object populated with the particular user's information. You could put the code to store the returned UserInfo instance in the HttpContext.Items Hashtable directly in each User Control and compiled control, like so:

... inside the User Control ...
... We need to get information about User X ...
// C# int X = 4; // See if User X is in the Items Hashtable UserInfo u = (UserInfo) HttpContext.Current.Items["user" + X.ToString()]; if (u == null) { // get the user and add it to the Items Hashtable u = DAL.GetUserByUserId(X); HttpContext.Current.Items["user" + X.ToString()] = u; } ' VB.NET... Dim X as Integer = 4 'See if User X is in the Items Hashtable Dim u as UserInfo = CType(HttpContext.Current.Items("user" & X), UserInfo) If u Is Nothing Then ' get the user and add it to the Items Hashtable u = DAL.GetUserById(X) HttpContext.Current.Items("user" & X) = u End If

Now, imagine that you have two User Controls on the page, both using the above code to access information about users. Assume the first User Control is rendered first, and it gets information about user X. Since the Items Hashtable's lifetime is just for the request, it will not have any cached user information in it, so the User Control will add a new UserInfo object into the Hashtable with the key userX. No benefit gained here. But when the second User Control gets information about user X, it can bypass the call down to the data access layer, since that information is cached in the Items Hashtable. The performance benefits are multiplied on pages with numerous User Controls and custom compiled controls that use similar bits of data.

Want to Learn More About Hashtables?
The HttpContext.Items is a Hashtable. A Hashtable is an associative array, one whose elements are indexed by a key value rather than by an ordinal index. Hashtables are interesting data structures, as they offer constant-time searches, when searching by the key. To learn more about Hashtables, and how they can offer such great performance, be sure to read: An Extensive Examination of Data Structures: Part 2.

A Better Approach - Moving the HttpContext.Items Check Out of the User Controls and Compiled Server Controls


While placing the code to access the HttpContext.Items Hashtable in each and every User Control and compiled server control will work, it's not ideal, since you have to repeat the code in each control. This is a poor choice for a couple reasons: first, you are tightly coupling the HttpContext.Items implementation to the User Controls/custom server controls, which would be a headache to have to change if you decided later not to use this approach; second, you have to have all of the User Controls and compiled custom controls agree on a same key name for the data they are caching (I used usersX in the example above). Having to remember the right name across all controls is, obviously, a recipe for bugs.

A better approach is to move the HttpContext.Items-specific code outside of the User Controls and compiled controls. One option is to move it into the data access layer. In the GetUserByUserId(int) method, for example, we could do the caching like so:

... inside the data access layer ...

// C#
public static UserInfo GetUserByUserId(int userID)
{
  // See if User X is in the Items Hashtable
  UserInfo u = (UserInfo) HttpContext.Current.Items["user" + X.ToString()];
  if (u == null)
  {
    // get the user info from the database and populate a UserInfo class...
    u = new UserInfo();    
    ...    
  }
  
  return u;
}


' VB.NET...
Public Shared Function GetUserByUserId(userID as Integer) as UserInfo
  'See if User X is in the Items Hashtable
  Dim u as UserInfo = CType(HttpContext.Current.Items("user" & X), UserInfo)
  If u Is Nothing Then
    ' get the user info from the database and populate a UserInfo class...
    u = New UserInfo
    ...
  End If
  
  Return u
End Function

Note: To get this to work you will need to make sure you have added using System.Web or Imports System.Web into your data access class, and that the assembly has a reference to the System.Web.dll assembly.

The one disadvantage with this approach is that it ties your data access layer to a Web application, since the HttpContext object is only accessible when the data access layer is operating through a Web application. If you need to use your data access layer for WinForms-based applications as well, it would be a smarter move to create another tier in your architecture, a "Web application data access layer" that resided between your ASP.NET Web application and the data access layer. This middle tier would utilize the HttpContext.Items Hashtable, thereby keeping the data access layer not tied to a particular application type.

Conclusion


In this article we examined how the HttpContext.Items Hashtable can be used as a per-request cache. This can offer improved performance for your ASP.NET application if you find that your pages have several User Controls, compiled controls, or custom business classes that each hit the database for the same (or similar) pieces of data. By using this per-request cache, you will only incur one trip to the database - for the redundant trips, the data can be quickly accessed from the Items Hashtable.

Happy Programming!

  • By Scott Mitchell



  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article