ASP 101 - Active Server Pages 101 - Web05
The Place ASP Developers Go!

Please visit our partners

Windows Technology Windows Technology
15 Seconds
ASP 101
ASP Wire
VB Forums
VB Wire
internet.commerce internet.commerce
Partners & Affiliates

ASP 101 is an site
ASP 101 is an site
Internet News
Small Business
Personal Technology

Corporate Info
Tech Jobs
E-mail Offers

ASP 101 News Flash ASP 101 News Flash

 Top ASP 101 Stories Top ASP 101 Stories
Connections, Commands, And Procedures
What is ASP?
VBScript Classes: Part 1 of N

Use variables to speed up collection access
Show All Tips >>
ASP 101 RSS Feed ASP 101 Updates

Building a Simple Page Caching System

Building a Simple Page Caching System

by John Peterson


Not too long ago I wrote an article about the different types of server-side caching you could use to increase performance of selected asp pages. (If you haven't read it yet, you might want to read it now since this article builds upon it.) At the request of our users, I'm now going to cover building a simple system to implement the page caching technique I discussed in that article.

The Concept

The basic premise is to build an include file that you can insert at the top of any asp page and have it cache a static copy of the page that automatically gets refreshed after a given period of time.

In order to accomplish this, we need a few things. The first is a way to retreive a copy of the page to cache. The next is a place to store that page. Then we need a mechinism to determine when to refresh the cache. Finally we need a system to alternate between showing the cached and non-cached versions.

Note: This system would be quite a bit less complex if the cache controlling page and the page to be cached were seperated, but in order to make this operate within the confines of an easy to use include file, this isn't really feasible... so bear with me as the code can get a little confusing.

Before we get to any code... let me walk you through the basic process. A request for the asp page comes in. The first decision is whether or not we can use a previously cached version. If we can then we use ASP 3.0's Server.Transfer command to point the user to it. If we can't then we use the dynamic version. When we use the dynamic version, it means the cache has expired so it only makes sense to update the cache. To do this we need to get a copy of the page. We do this by requesting the page via an http component with a special QueryString that tells the page to give us the dynamic version, but also tells it not to initiate another update. If we didn't we'd end up in an infinite loop of updating the cached version and instead of reducing the load and increasing performance, we'd increase the load and probably bring the server to it's knees so I'm warning you now... be very careful when tinkering with the script!

The Code

Okay, so now that I've given you the quick overview, here's the code. Instead of giving you it piece by piece with a paragraph thrown in here and there, I'm just posting the one big block of code and commenting heavily to try and make things easier to follow and understand.

' Declare our variables
' To try and cut down on possible name collisions
' with existing scripts, I've prefixed them w/ caching
Dim cachingDynamicPageURL
Dim cachingStaticPageURL
Dim cachingForceRefresh
' Get the URLs of the pages.
' I use .htm for the cached version of our .asp files, but
' you could replace it with any extension if you have htm
' files on your server that you're afraid of overwriting.
cachingDynamicPageURL = Request.ServerVariables("URL")
cachingStaticPageURL  = Replace(cachingDynamicPageURL, ".asp", ".htm")
' Here's the basic logic... the implementation is
' mainly contained in the functions which follow:
' This first conditional decides whether or not the
' cached version is recent enough to show.  This is
' where the timeout (in minutes) should be changed
' if needed.
If PageIsFresh(1) Then
	' Simply a wrapper for our Server.Transfer command.
	' Transfers control to the cached page and stops
	' execution of this one.
	' If we need to do an update then the request to
	' update can't also initiate an update... so this
	' checks to see if one is already in progress.
	If PageIsBeingRefreshed() Then
		' Does the actual update to the cache file.
	End If
	' The rest of the remaining script continues to process.
End If
' *** Begin Functions *************************************
Function PageIsFresh(iTimeInMinutes) ' As Boolean
	Dim dLastUpdated
	Dim bPageIsFresh
	' Check and see when we last updated... I use a
	' variable based on the page name so we can use
	' this for lots of files on the same server.
	dLastUpdated = Application(GetAppVarName())
	' If the last update variable is blank... set a
	' default value of Jan 1, 2000
	If dLastUpdated = "" Then
		dLastUpdated = CDate("January, 1, 2000")
	End If
	' Compare Now to the last updated date and set
	' the flag indicating if the page has been
	' updated within the given timeframe.
	If DateDiff("n", dLastUpdated, Now()) < iTimeInMinutes Then
		bPageIsFresh = True
		bPageIsFresh = False
	End If
	' Debugging Line
	'Response.Write bPageIsFresh & "<br />"
	' Override the value we just determined based on
	' the Application var if we find the appropriate QS...
	' which is: ?nocache=true
	If bPageIsFresh = True Then
		' A little difficult to read and understand:
		'bPageIsFresh = _
		'  (Not(LCase(Request.QueryString("nocache")) = "true"))
		' Longer simpler version.
		If LCase(Request.QueryString("nocache")) = "true" Then
			bPageIsFresh = False
			' Somewhat pointless since we know it's already
			' True, but what the hell.
			bPageIsFresh = True
		End If
	End If
	' Set the global flag telling us if we need to update
	' based on the current "freshness" of the page.
	If bPageIsFresh Then
		cachingForceRefresh = False
		cachingForceRefresh = True
	End If
	' Ok... the horrible term of "fresh" is done
	' being used now.
	PageIsFresh = bPageIsFresh
End Function
Function ShowCachedPage() ' None
End Function
Function PageIsBeingRefreshed() ' As Boolean
	Dim bRefresh
	' Default to the value set when we checked if
	' the page was "fresh" enough
	bRefresh = cachingForceRefresh
	' If it's true then we need to check and make
	' sure we're not overriding it to false via QS.
	' QS command is: refresh="false"
	If bRefresh Then
		' Again a little difficult to read and understand:
		'bRefresh = _
		'  LCase(Request.QueryString("refresh")) = "true"
		' Again a simpler version
		If LCase(Request.QueryString("refresh")) = "false" Then
			bRefresh = False
			' Again somewhat pointless since we know it's
			' already True, but again... what the hell.
			' Haven't we done this once already?  ;)
			bRefresh = True
		End If
	End If
	' Set the return value
	PageIsBeingRefreshed = bRefresh
End Function
' Finally a simple, straight-forward function... sort of!
Function UpdateCachedPage() ' None
	Dim strFullURL, strFullPath
	Dim objXmlHttp, strHTML
	Dim objFSO, objFile
	' Build the URL of our cache refreshing data request.
	' Be SURE to include the nocache and refresh line.
	strFullURL = "http://" & Request.ServerVariables("SERVER_NAME")
	strFullURL = strFullURL & cachingDynamicPageURL
	strFullURL = strFullURL & "?nocache=true&refresh=false"
	' Get out full file system path.
	strFullPath = Server.MapPath(cachingStaticPageURL)
	' Send the request and get the result as text.
	' I assume it's good... you might want to check for
	' something it's supposed to contain and abort
	' if it's not there.
	Set objXmlHttp = Server.CreateObject("Msxml2.ServerXMLHTTP") "GET", strFullURL, False
	strHTML = objXmlHttp.responseText
	Set objXmlHttp = Nothing
	' Debugging line.
	'Response.Write strHTML
	' Take our recently acquired text and write it to a file.
	Set objFSO = Server.CreateObject("Scripting.FileSystemObject")
	Set objFile = objFSO.OpenTextFile(strFullPath, 2, True)
	' I do a replace for illustration in the sample code:
	objFile.WriteLine Replace(strHTML, "Dynamic", "Static")
	' Here's the line without it.
	'objFile.WriteLine strHTML
	Set objFile = Nothing
	Set objFSO = Nothing
	' Update our last updated date!
	' Wheh... try saying that 3 times quickly.  ;)
	Application(GetAppVarName()) = Now()
End Function
' Wrapper for the way we determine our application
' variable name since this might want to be changed.
Function GetAppVarName() ' As String
	Dim strTemp
	' Read in from global var
	strTemp = cachingDynamicPageURL
	GetAppVarName = ConvertPageNameToVarName(strTemp)
End Function
' Pages can contain characters that variable names can't so I
' built this little function to weed them out.  I can't think
' of any other common file characters that don't work, but if
' you run across any simply update this function.
Function ConvertPageNameToVarName(strPageName) ' As String
	Dim strTemp
	strTemp = strPageName
	strTemp = Replace(strTemp, "/", "~s~")
	strTemp = Replace(strTemp, ".", "~p~")
	ConvertPageNameToVarName = strTemp
End Function
' This can't be expected to always reverse the above, but for
' simple filenames it should work most of the time.  Not that
' I can think of any reason you'd need to do it anyway, but
' just in case it ever comes up... here it is.
Function ConvertVarNameToPageName(strPageName) ' As String
	Dim strTemp
	strTemp = strPageName
	strTemp = Replace(strTemp, "~s~", "/")
	strTemp = Replace(strTemp, "~p~", ".")
	ConvertVarNameToPageName = strTemp
End Function
' *** End Functions ***************************************


The code was written to be pretty easy to implement. Simply upload the include file, change the amount of time the caching lasts to an appropriate value for your content, and add a line for the include file to the top of the asp page you want to cache.

Here's a basic sample script that I've included in the zip file.

<%@ Language=VBScript %>
Option Explicit
Response.Buffer = False
<!-- #include file="caching.asp" -->
<title>Dynamic Page</title>
<h2>Dynamic Page</h2>
<p>Page Rendered: <%= Now() %></p>

There are a couple potential problems that might arise during the installation of this code. The first has to do with the NTFS security settings on the caching file. The anonymous internet user will need change (RWD) access to it in order to refresh the cache. The other issue has to do with multiple users refreshing the cache simultaneously. While this shouldn't be too big a deal, it could cause some problems on extremely busy sites so if you're expecting extreme traffic, you'll probably be better off implementing this in seperate pages and devising a little more sophisticated locking mechinism.

Possible Problems

The code uses direct access to global varibales from within functions. I normally frown upon this type of thing, but it was quick and easy and well... I got lazy. So sue me... it's not as pretty as usual, but it works. ;)

Another issue is that I'm still having the same trouble with the ServerXMLHTTP object that I mention in the code in this sample, so until someone gets me a fix, you might have to use XMLHTTP, but be forewarned it really isn't server safe so you'd probably be better off using a 3rd party component or the Internet Transfer Control for the http work... at least for now.


It may not be the fastest or best caching solution around, but like I said in the title, it is a simple asp-only solution that enables you to do page level caching with a minimum of fuss and almost no setup.

That's all I've got for now folks. Here's the code in a zip file so you can download an play with it. Once we solve this ServerXMLHTTP issue, I'll put up a sample so you can play with it, but for now that's all she wrote.

Additional Information:
Server Side Caching Options - The article that led to this one

Related Products:
Post Point Software - Maker of XBuilder and XCache commercial caching software
WebGecko - Maker of Active Page Generator (APGen) commercial caching software

Home |  News |  Samples |  Articles |  Lessons |  Resources |  Forum |  Links |  Search |  Feedback
The Network for Technology Professionals



Legal Notices, Licensing, Permissions, Privacy Policy.
Advertise | Newsletters | E-mail Offers