By Scott Mitchell
HttpContext.Items - a Per-Request Cache Store
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.HttpContextobject is created. This class contains information about the request. For example, the
HttpContextclass has the "intrinsic" objects you're familiar with:
HttpContextcontains information about the current user making the request in its
Userproperty, it stores information about any errors that have occurred in its
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
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.
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
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
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
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
HttpContextobject. 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
Items collection provides a repository for stashing objects on a
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
Hashtable directly in each User Control and compiled control, like so:
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
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?|
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.ItemsHashtable 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.Itemsimplementation 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
usersXin 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
for example, we could do the caching like so:
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
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.
In this article we examined how the
HttpContext.ItemsHashtable 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