Published: Wednesday, June 9, 2004
HttpContext.Items - a Per-Request Cache Store
By Scott MitchellIntroduction
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.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 solidapplication
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. |
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: |
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
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: |
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
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.ItemsHashtable, 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!
No comments:
Post a Comment