Welcome to Part 9 of my series on Caching Enhancements in ColdFusion 9. Today we're going to cover something called dependent template caching. Strange name, I know. If you remember back from Part 7, we said that by default, when you cache a web page or page fragment with the cfcache tag, that page/fragment goes into the cache and is retrieved for any subsequent visits to that page. The same content is returned for everyone. We also covered a method for caching pages based on unique URL parameters such that different versions of the same page (say a product display page) would be cached and retrieved from the cache based on URL parameters. But what about page/page fragments that vary based on other variables that aren't passed in via URL? This is where dependent template caching comes in.

Dependent template caching allows you to specify a variable or list of variables to "watch" for changes. If the value of one of these variables changes from the first page or fragment that was cached, ColdFusion will create a new variant for the page/fragment and store that in the cache as well. This is all handled by using the new dependsOn attribute of the cfcache tag in ColdFusion 9. If you are reading this and wondering where this might be useful, you aren't alone. When I first read about this feature in the ColdFusion docs, I misunderstood the intent of the attribute and how it's supposed to work. Here's what the Coldfusion 9 docs have to say about dependsOn:

A comma separated list of variables. If any of the variable values change, ColdFusion updates the cache. This attribute can take an expression that returns a list of variables.

I think the key to the misunderstanding people have about this feature is in the part that says "If any of the variable values change, ColdFusion updates the cache." To me, updating the cache means replacing an old/expired/changed value with a new one. It's a one for one swap of items in the cache. Out with the old and in with the new. But this isn't what happens when you use dependsOn. What the docs should say is that when a variable value changes, ColdFusion creates a new entry in the cache for the changed item so that both the original page/fragment as well as the new page/fragment are now in the cache. Here's a quick example to illustrate how this works:

view plain print about
1<cfset y=true>
2
3<cfflush>
4<cfloop index="x" from="1" to="5">
5<cfif x is 3>
6    <cfset y=false>
7<cfelse x is 5>
8    <cfset y=true>
9</cfif>
10
11
12<cfset sleep(1000)>
13<cfflush interval="10">
14<cfcache action="serverCache" dependsOn="#y#" stripWhiteSpace="true">
15<cfoutput>
16I'm cached dynamic data: #now()# <br/>
17</cfoutput>
18</cfcache>
19
20<!--- dump what's in the template cache --->
21<cfdump var="#getAllTemplateCacheIDs()#">
22</cfloop>

If you run this code, you should see something that looks like this:

What this code does is create a page fragment and cache it within a loop. The cfcache tag is set to watch a variable called y for changes. The value of y is initially set to true. There's also some conditional code in the loop which waits for the third and fifth iterations of the loop to fire. We'll get to that in just a moment. For now, let's step through each iteration of the loop and discuss what's happening. During the first iteration of the loop, the fragment is added to the cache. During the 2nd iteration of the loop, the fragment is pulled from the cache and displayed. During the third iteration of the loop, the value of x is 3 and our cfif statement fires, updating the value of y to false. Because y is the value we set to watch in the dependsOn attribute of our cfcache tag and it has now changed from true to false, this signals ColdFusion to go ahead and cache the version of the loop output we're now on for iteration 3 of the loop. This is where we end up with a 2nd fragment in the cache, not an update to the existing fragment in the cache. The fourth iteration of the loop also displays the second cached fragment since the value of y is still false. For the fifth and final iteration of the loop, our conditional code within the loop fires again. This time it sets y back to false, a value which we already have a fragment stored in the cache. ColdFusion knows to go grab the fragment for false from the cache and displays it.

There's one other thing to note in here. I didn't think to include this in any of the previous posts on the template cache so I've decided to add it here. If you look at the end of the cfcache tag in our example, you'll notice a parameter you probably haven't seen before: stripWhiteSpace. This is an optional parameter that only works if you are using the template cache to cache page fragments. Setting it to true (it's false by default) tells Coldfusion to strip any unnecessary whitespace from the fragment before storing it in the cache.

While this is a good example of the mechanics of dependent caching, it's not really a practical example. For that, let's consider a real world example where you would want to make use of dependent caching. Say you have an application that requires authentication. The main landing page for the application is personalized based on who is logged in. In this case, you can't cache a single version of the main page as you wouldn't want it to say "Hello Tom" when Mary logs in. Sure you could solve this by passing the username along in the URL, but you probably don't want to do that – who wants to deal with all of the extra validation code to make sure someone doesn't go and change that URL variable to someone else's username. No, in this case, you would probably be using session variables in your application to maintain persistence, and session variables are a perfect use case for dependent caching. Here, we could set dependsOn to watch something that uniquely identifies a user and when that changes (a different user is logged in), the personalized version of the page for them could be added to the cache. Let's take a look at some simple code that implements this idea. The first thing we'll need is an Application.cfc file to setup session management and handle security basics for us:

view plain print about
1<cfcomponent output="false">
2    <cfset this.name = "dependentCaching" />
3    <cfset this.sessionManagement = true>
4
5 <cffunction name="onRequestStart" eeturntype="boolean" output="false">
6     <cfif StructKeyExists( URL, "logout" )>
7         <cfset this.onSessionStart() />
8     </cfif>
9
10     <cfreturn true />
11 </cffunction>
12
13    <cffunction name="onRequest" returnType="void" output="true">
14     <cfargument name="Page" type="string" required="true">
15
16        <cfif session.loggedIn>
17            <cfinclude template="#arguments.Page#">
18        <cfelse>
19            <cfinclude template="login.cfm">
20        </cfif>
21
22        <cfreturn />
23    </cffunction>
24
25    <cffunction name="onSessionStart" returnType="void" output="false">
26     <cfset session.loggedIn = false>
27    </cffunction>
28
29</cfcomponent>

This code gives our application a name and turns on session management. If also has an onRequestStart() method that looks for a URL variable called logout, and if it finds one it fires off the onSessionStart() method, effectively logging the user out be changing the value of session.loggedIn to false.

The onRequest() method handles the check to see if a user is authenticated for a requested page. If session.loggedIn is true, the page they were requesting is included. Otherwise, we assume that the user is not logged in and include the login form instead.

The onSessionStart() method fires at the beginning of a user's session and sets their logged in status to false by default.

Remember that this is just a simple example and for that reason does not contain all of the code you would use to implement something like this in real life (validation checks, error handling, etc.).

The next file we need is our login.cfm page:

view plain print about
1<cfif isDefined('form.submit')>
2 <cfset session.loggedIn = true>
3 <cfset session.userName = form.username>
4
5 <!--- send the user back to the main page --->
6 <cflocation url="index.cfm" addtoken="false" />
7
8<cfelse>
9
10 <cfoutput>
11 <form method="post">
12 Name: <input type="text" name="userName"><br />
13 Password: <input type="password" name="password"><br />
14 <input type="submit" name="Submit" value="Submit">
15 </form>
16 </cfoutput>
17</cfif>

This page is just a simple login form. It firsts checks to see if it was called via a form submit, and if so sets the value of session.loggedIn to true. It also sets another session variable to hold the user's username. After the variables are set, the user is redirected to the main landing page for the application (index.cfm) Again, if this were a real application we would have an actual login check here but for the purposes of this example we're just assuming that any username/password combo is valid.

If the user arrived at the page directly from the onRequest() method of our Application.cfm page, we know that they have not yet logged in so we display a login form for them. When they submit this, the page submits to itself and the code we previously discussed fires, logging the user in and redirecting them to the main application page. Here's the code for the main index.cfm page:

view plain print about
1<cfcache action="serverCache" timespan="#createTimeSpan(0,0,5,0)#" dependsOn="session.username">
2<cfoutput>
3Welcome #session.username#
4
5<p>This is your personalized page.</p>
6
7<p>Timestamp: #timeFormat(now(), 'hh:mm:ss')#</p>
8
9<p><a href="index.cfm?logout=true">Logout</a></p>
10</cfoutput>

There's not a whole lot going on here. All we do is set a cfcache tag at the top of the page telling ColdFusion that we want to cache the contents of the entire page. A timespan of 5 minutes is set just to keep the example from staying in the cache forever. Notice we also set dependsOn=session.username". This is where the magic happens. What we've done is told ColdFusion is that every time a different user tries to call this page, it should first check the cache to see if there's already a page stored for this user and if so, grab and use that version. If not, it should generate a new version of the page and cache that value for later use.

If you want to see this in action, go ahead and open the index.cfm page in your browser. You should be redirected to the login form. You can enter anything you like for the username and password. Once you submit the login form, you should be redirected to a personalized version of the index.cfm page. Note the value of the timestamp.

Now go ahead and click on the logout link. This will clear your session and cause the login form to display again. Try logging in using a different username this time. After submitting, you'll again be redirected to a personalized version of index.cfm.

Logout again and repeat the process again but this time use the username you entered the first time. When you submit and are taken to the index.cfm page you should notice that the value of the timestamp is the same as the first time you logged in as this user. This is because ColdFusion saw that session.userName changed and found a page in the cache that corresponded to the username you logged in with (the username becomes part of the key for the page in the template cache). If you want to see that there are two distinct pages in the cache, just create a new ColdFusion page in the same directory as the rest of your application and add dump the template cache using this code:

view plain print about
1<cfdump var="#getAllTemplateCacheIds()#">

You'll end up with something like this:

As you can see, each individual user has their own copy of the index.cfm page in the cache thanks to dependsOn.

I hope these examples were straightforward and useful enough to demonstrate the usage and power of dependent caching in ColdFusion 9. This is the last post on the template cache I have planned for the series. In Part 10, we'll start to take a look at the object cache in ColdFusion 9 before moving on to more advanced topics.